├── .gitignore ├── History.md ├── compile.js ├── src ├── helpers │ ├── rgbToHex.js │ └── transform │ │ └── createTransformGroup.js ├── config.js ├── naming.js ├── layers │ ├── solid │ │ └── solid.js │ ├── transformer.js │ ├── composition.js │ ├── layer.js │ ├── shape │ │ ├── shape.js │ │ └── drawable.js │ └── masker.js ├── targets │ └── targets.js ├── pathData.js ├── index.js ├── avd │ └── avd.js ├── node.js └── property.js ├── README.md ├── package.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | exports/ 2 | node_modules/ 3 | test.js 4 | test.xml -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## V 1.0.6 2 | - FIX: Changed nesting clip paths to siblings of nested content -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var browserify = require("browserify"); 3 | browserify("./src/index.js") 4 | .transform("babelify", {presets: ["es2015"], global: true}) 5 | .bundle() 6 | .pipe(fs.createWriteStream("lib/build.js")); -------------------------------------------------------------------------------- /src/helpers/rgbToHex.js: -------------------------------------------------------------------------------- 1 | function componentToHex(c) { 2 | c = Math.round(c); 3 | var hex = c.toString(16); 4 | return hex.length == 1 ? "0" + hex : hex; 5 | } 6 | 7 | function rgbToHex(r, g, b) { 8 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 9 | } 10 | 11 | module.exports = rgbToHex -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | var long_config = { 2 | naming: 'long', 3 | xml_formatted: true 4 | } 5 | 6 | var short_config = { 7 | naming: 'short', 8 | xml_formatted: false 9 | } 10 | 11 | var config; 12 | if(process.env.ENVIRONMENT === 'DEV') { 13 | config = long_config; 14 | } else { 15 | config = short_config; 16 | } 17 | 18 | module.exports = config -------------------------------------------------------------------------------- /src/naming.js: -------------------------------------------------------------------------------- 1 | var config = require('./config') 2 | 3 | var long_naming = { 4 | GROUP_NAME: '_GROUP', 5 | TRANSFORM_NAME: '_TRANSFORM', 6 | LAYER_NAME: '_LAYER', 7 | DRAWABLE_NAME: '_DRAWABLE', 8 | PATH_NAME: '_PATH', 9 | ROOT_NAME: '_ROOT', 10 | PARENT_NAME: '_PARENT', 11 | CLIP_NAME: '_CLIP', 12 | SOLID_NAME: '_SOLID', 13 | TIME_NAME: '_TIME' 14 | } 15 | var short_naming = { 16 | GROUP_NAME: '_G', 17 | TRANSFORM_NAME: '_T', 18 | LAYER_NAME: '_L', 19 | DRAWABLE_NAME: '_D', 20 | PATH_NAME: '_P', 21 | ROOT_NAME: '_R', 22 | PARENT_NAME: '_N', 23 | CLIP_NAME: '_C', 24 | SOLID_NAME: '_S', 25 | TIME_NAME: '_M' 26 | } 27 | 28 | var naming = config.naming === 'short' ? short_naming : long_naming 29 | 30 | module.exports = naming -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bodymovin-to-avd 2 | Bodymovin to AVD converter 3 | 4 | #Example of usage 5 | ````javascript 6 | var avd_converter = require('./src/index.js'); 7 | var fs = require('fs'); 8 | 9 | fs.readFile("./exports/jsons/data.json", "utf8", function(error, data){ 10 | process.on('unhandledRejection', function(err, promise) { 11 | console.error('Unhandled rejection (promise: ', promise, ', reason: ', err, ').'); 12 | }); 13 | var prom = avd_converter(JSON.parse(data)) 14 | prom.then(function(xml){ 15 | fs.writeFile("./test.xml", xml, function(err) { 16 | if(err) { 17 | return console.log(err); 18 | } 19 | 20 | console.log("The file was saved!"); 21 | }); 22 | }).catch(function(err){ 23 | console.log('catch'); 24 | }); 25 | 26 | }) 27 | ```` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bodymovin-to-avd", 3 | "version": "1.0.8", 4 | "description": "Bodymovin to AVD converter", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "browserify src/index.js -o lib/build.js -t [ babelify --presets [ babel-preset-es2015 ] ]" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/bodymovin/bodymovin-to-avd.git" 13 | }, 14 | "author": "Hernan Torrisi (http://codepen.io/airnan/)", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/bodymovin/bodymovin-to-avd/issues" 18 | }, 19 | "homepage": "https://github.com/bodymovin/bodymovin-to-avd#readme", 20 | "dependencies": { 21 | "transformation-matrix": "^1.4.0", 22 | "transformatrix": "^1.1.1", 23 | "xml": "^1.0.1" 24 | }, 25 | "devDependencies": { 26 | "babel-cli": "^6.24.1", 27 | "babel-preset-es2015": "^6.24.1", 28 | "babelify": "^7.3.0", 29 | "browserify": "^14.4.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 hernan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/layers/solid/solid.js: -------------------------------------------------------------------------------- 1 | var node = require ('../../node'); 2 | var naming = require ('../../naming'); 3 | var layer = require ('../layer'); 4 | 5 | function solid(layerData) { 6 | 7 | var state = { 8 | layerData: layerData 9 | } 10 | 11 | function createNodeInstance(grouper, groupName) { 12 | var layerData = state.layerData; 13 | var attributes = []; 14 | attributes.push({ 15 | key: 'android:fillColor', 16 | value: state.layerData.sc 17 | }) 18 | attributes.push({ 19 | key: 'android:pathData', 20 | value: 'M0,0 L' + layerData.sw + ',0 L' + layerData.sw + ',' + layerData.sh + ' L0,' + layerData.sh + 'z' 21 | }) 22 | var path = node.createNodeWithAttributes('path', attributes, groupName + naming.SOLID_NAME); 23 | if(!(layerData.ks && layerData.ks.o && layerData.ks.o.a === 0 && layerData.ks.o.k === 0)) { 24 | node.nestChild(grouper, path); 25 | } 26 | } 27 | 28 | function processData() {} 29 | 30 | var factoryInstance = { 31 | createNodeInstance: createNodeInstance, 32 | processData: processData 33 | }; 34 | 35 | Object.assign(factoryInstance, layer(state)); 36 | 37 | return factoryInstance; 38 | } 39 | 40 | module.exports = solid; -------------------------------------------------------------------------------- /src/targets/targets.js: -------------------------------------------------------------------------------- 1 | var node = require ('../node'); 2 | var _targets = []; 3 | 4 | function resetTargets(){ 5 | _targets.length = 0; 6 | } 7 | 8 | function addTarget(target){ 9 | var firstLeave = node.getLastLeaves(target)[0]; 10 | if (node.getTagName(firstLeave) === 'set') { 11 | return; 12 | } 13 | _targets.push(target); 14 | } 15 | 16 | function getTargetByNameAndProperty(name, property) { 17 | var i = 0, len = _targets.length; 18 | while(i < len) { 19 | if(node.getAttribute(_targets[i], 'android:name') === name) { 20 | var aapt_attr = node.getChild(_targets[i], 'aapt:attr'); 21 | var set = node.getChild(aapt_attr, 'set'); 22 | var objectAnimator = node.getChild(set, 'objectAnimator'); 23 | if(node.getAttribute(objectAnimator, 'android:propertyName') === property) { 24 | return _targets[i]; 25 | } 26 | } 27 | i += 1; 28 | } 29 | return null; 30 | } 31 | 32 | function buildTargets(avd) { 33 | var target; 34 | var i, len = _targets.length; 35 | for(i = 0; i < len; i += 1) { 36 | target = _targets[i]; 37 | node.nestChild(avd, target); 38 | } 39 | } 40 | 41 | module.exports = { 42 | resetTargets: resetTargets, 43 | addTarget: addTarget, 44 | getTargetByNameAndProperty: getTargetByNameAndProperty, 45 | buildTargets: buildTargets 46 | }; -------------------------------------------------------------------------------- /src/pathData.js: -------------------------------------------------------------------------------- 1 | var Matrix = require('transformatrix'); 2 | 3 | var _matrix = new Matrix(); 4 | 5 | function createPathData(data, matrix) { 6 | if(!matrix) { 7 | matrix = _matrix; 8 | } 9 | var i, len = data.v.length; 10 | var pathValue = ''; 11 | var pt; 12 | for( i = 0; i < len - 1; i += 1) { 13 | if(i === 0) { 14 | pt = matrix.transformPoint(data.v[0][0], data.v[0][1]); 15 | pathValue += 'M' + roundValue(pt[0]) + ' ' + roundValue(pt[1]); 16 | } 17 | pt = matrix.transformPoint(data.o[i][0] + data.v[i][0], data.o[i][1] + data.v[i][1]); 18 | pathValue += ' C' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 19 | pt = matrix.transformPoint(data.i[i + 1][0] + data.v[i + 1][0], data.i[i + 1][1] + data.v[i + 1][1]); 20 | pathValue += ' ' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 21 | pt = matrix.transformPoint(data.v[i + 1][0], data.v[i + 1][1]); 22 | pathValue += ' ' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 23 | } 24 | if(data.c) { 25 | pt = matrix.transformPoint(data.o[i][0] + data.v[i][0], data.o[i][1] + data.v[i][1]); 26 | pathValue += ' C' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 27 | pt = matrix.transformPoint(data.i[0][0] + data.v[0][0], data.i[0][1] + data.v[0][1]); 28 | pathValue += ' ' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 29 | pt = matrix.transformPoint(data.v[0][0], data.v[0][1]); 30 | pathValue += ' ' + roundValue(pt[0]) + ',' + roundValue(pt[1]); 31 | pathValue += 'c'; 32 | } 33 | pathValue += ' '; 34 | return pathValue; 35 | } 36 | 37 | function roundValue(val) { 38 | return Math.round(val*100)/100; 39 | } 40 | 41 | module.exports = createPathData; -------------------------------------------------------------------------------- /src/layers/transformer.js: -------------------------------------------------------------------------------- 1 | var node = require ('../node'); 2 | var naming = require ('../naming'); 3 | var createTransformGroup = require ('../helpers/transform/createTransformGroup'); 4 | 5 | function transformer(state) { 6 | var transforms = []; 7 | 8 | function transform(transformData) { 9 | transforms.push(transformData); 10 | } 11 | 12 | function transformNode(node) { 13 | var i, len = transforms.length; 14 | for(i = 0; i < len; i += 1) { 15 | 16 | } 17 | } 18 | 19 | function setSiblings(_siblings) { 20 | state.siblings = _siblings; 21 | } 22 | 23 | function getLayerDataByIndex(index) { 24 | var siblings = state.siblings; 25 | var i = 0, len = siblings.length; 26 | while( i < len) { 27 | if(siblings[i].ind === index) { 28 | return siblings[i]; 29 | } 30 | i += 1; 31 | } 32 | } 33 | 34 | function buildParenting(parent, group, name, useContainerFlag) { 35 | if(parent !== undefined) { 36 | name = name + naming.PARENT_NAME + '_' + parent; 37 | //var parentGroup = node.createNode('group', name); 38 | 39 | var parentData = getLayerDataByIndex(parent); 40 | var nestedArray; 41 | if (useContainerFlag) { 42 | nestedArray = createTransformGroup(name, parentData.ks, state.timeOffset, state.frameRate, group); 43 | } else { 44 | nestedArray = [group].concat(createTransformGroup(name, parentData.ks, state.timeOffset, state.frameRate, null)); 45 | } 46 | var containerParentGroup = node.nestArray(nestedArray); 47 | containerParentGroup = buildParenting(parentData.parent, containerParentGroup, name, false); 48 | 49 | return containerParentGroup; 50 | } 51 | return group; 52 | } 53 | 54 | return { 55 | transform: transform, 56 | buildParenting: buildParenting, 57 | setSiblings: setSiblings 58 | } 59 | } 60 | 61 | module.exports = transformer; -------------------------------------------------------------------------------- /src/layers/composition.js: -------------------------------------------------------------------------------- 1 | var layer = require ('./layer'); 2 | var node = require ('../node'); 3 | var shapeFactory = require ('../layers/shape/shape'); 4 | var solidFactory = require ('../layers/solid/solid'); 5 | var naming = require('../naming'); 6 | 7 | function composition(compositionData, assets) { 8 | 9 | var compLayersData = compositionData.layers || getCompositionLayers(compositionData.refId, assets); 10 | 11 | var state = { 12 | inPoint: compositionData.ip || 0, 13 | outPoint: compositionData.op || 0, 14 | startPoint: compositionData.st || 0, 15 | layerData: compositionData, 16 | layers: [] 17 | } 18 | 19 | function getCompositionLayers(compId, assets, layers) { 20 | var i = 0, len = assets.length; 21 | while (i < len) { 22 | if(assets[i].id === compId) { 23 | return assets[i].layers; 24 | } 25 | i += 1; 26 | } 27 | return []; 28 | } 29 | 30 | function createNodeInstance(grouper, groupName){ 31 | var layers = state.layers; 32 | var i, len = layers.length; 33 | for (i = len - 1; i >= 0; i -= 1) { 34 | node.nestChild(grouper, layers[i].exportNode(groupName + naming.LAYER_NAME + '_' + i, state.workAreaOffset)); 35 | } 36 | } 37 | 38 | function processData() { 39 | var i, len = compLayersData.length; 40 | var layer; 41 | for(i = 0; i < len; i += 1) { 42 | if(compLayersData[i].ty === 4) { 43 | layer = shapeFactory(compLayersData[i]); 44 | } else if(compLayersData[i].ty === 0) { 45 | layer = composition(compLayersData[i], assets); 46 | } else if(compLayersData[i].ty === 1) { 47 | layer = solidFactory(compLayersData[i]); 48 | } else { 49 | layer = null; 50 | } 51 | if(layer){ 52 | layer.setTimeOffset(state.timeOffset + state.startPoint); 53 | layer.setSiblings(compLayersData); 54 | layer.processData(); 55 | state.layers.push(layer); 56 | } 57 | } 58 | return factoryInstance; 59 | } 60 | 61 | var factoryInstance = { 62 | createNodeInstance: createNodeInstance, 63 | processData: processData 64 | }; 65 | Object.assign(factoryInstance, layer(state)); 66 | 67 | return factoryInstance; 68 | } 69 | 70 | module.exports = composition; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('xml'); 4 | var fs = require('fs'); 5 | var node = require('./node'); 6 | var avdFactory = require('./avd/avd'); 7 | var config = require('./config'); 8 | 9 | function addTargetsToAVD(targets, avd) { 10 | var target; 11 | var i, len = targets.length; 12 | for(i = 0; i < len; i += 1) { 13 | target = targets[i]; 14 | node.nestChild(avd, target); 15 | } 16 | } 17 | 18 | function createAnimatedVectorObject() { 19 | var attributes = [{ 20 | key: 'xmlns:android', 21 | value: 'http://schemas.android.com/apk/res/android' 22 | },{ 23 | key: 'xmlns:aapt', 24 | value: 'http://schemas.android.com/aapt' 25 | }] 26 | var nodeElem = node.createNodeWithAttributes('animated-vector', attributes); 27 | return nodeElem; 28 | } 29 | 30 | function createAAPTVectorDrawable(animation, targets) { 31 | var aapt = node.createNodeWithAttributes('aapt:attr',[{key:'name', value:'android:drawable'}]); 32 | var vectorDrawable = createVectorDrawable(animation.w, animation.h); 33 | layer.addLayers(vectorDrawable, animation.layers, animation, targets, 'root_', 0); 34 | node.nestChild(aapt, vectorDrawable); 35 | return aapt; 36 | } 37 | 38 | function correctTargetsTimes(targets, framerate) { 39 | var i, len = targets.length; 40 | var j, jLen; 41 | var target, aapt_attr, set, setChildren, animator; 42 | var duration, startOffset; 43 | for(i = 0; i < len; i += 1) { 44 | target = targets[i]; 45 | aapt_attr = node.getChild(target, 'aapt:attr'); 46 | set = node.getChild(aapt_attr, 'set'); 47 | setChildren = node.getChildren(set); 48 | jLen = setChildren.length; 49 | for(j = 1; j < jLen; j += 1) { 50 | animator = setChildren[j]; 51 | duration = node.getAttribute(animator, 'android:duration'); 52 | startOffset = node.getAttribute(animator, 'android:startOffset'); 53 | if(duration) { 54 | node.addAttribute(animator, 'android:duration', Math.round(duration/framerate*1000)); 55 | } 56 | if(startOffset) { 57 | node.addAttribute(animator, 'android:startOffset', Math.round(startOffset/framerate*1000)); 58 | } 59 | } 60 | } 61 | } 62 | 63 | function createVectorDrawable(width, height) { 64 | var attributes = [{ 65 | key: 'android:height', 66 | value: height + 'dp' 67 | },{ 68 | key: 'android:width', 69 | value: width + 'dp' 70 | },{ 71 | key: 'android:viewportHeight', 72 | value: height 73 | },{ 74 | key: 'android:viewportWidth', 75 | value: width 76 | }]; 77 | var nodeElement = node.createNodeWithAttributes('vector', attributes, ''); 78 | return nodeElement; 79 | } 80 | 81 | /** 82 | * Adds commas to a number 83 | * @param {object} animation 84 | * @return {string} 85 | */ 86 | module.exports = function(animation) { 87 | return new Promise(function(resolve, reject){ 88 | var targets = []; 89 | // 90 | var _avd = avdFactory(); 91 | _avd.processAnimation(animation) 92 | .then(_avd.exportNode) 93 | .then(function(avdNode){ 94 | var format = config.xml_formatted ? ' ' : ''; 95 | var xmlString = xml(avdNode, format); 96 | resolve(xmlString); 97 | 98 | }).catch(function(err){ 99 | console.log(err.stack) 100 | reject(err.stack); 101 | }); 102 | // 103 | }) 104 | }; -------------------------------------------------------------------------------- /src/layers/layer.js: -------------------------------------------------------------------------------- 1 | var naming = require('../naming'); 2 | var node = require ('../node'); 3 | var masker = require ('./masker'); 4 | var property = require ('../property'); 5 | var targets = require ('../targets/targets'); 6 | var transformer = require ('./transformer'); 7 | var createTransformGroup = require ('../helpers/transform/createTransformGroup'); 8 | 9 | function layer(state) { 10 | 11 | state.timeOffset = 0; 12 | state.workAreaOffset = 0; 13 | 14 | function setTimeOffset(_timeOffset) { 15 | state.timeOffset = _timeOffset; 16 | return this; 17 | } 18 | 19 | function setWorkAreaOffset(_workAreaOffset) { 20 | state.workAreaOffset = _workAreaOffset; 21 | return this; 22 | } 23 | 24 | function exportNode(name, parentWorkAreaOffset) { 25 | var groupName = name + naming.GROUP_NAME; 26 | var masksGroup = factoryInstance.getMasks(name); 27 | var gr; 28 | if(masksGroup) { 29 | gr = masksGroup; 30 | var leaves = node.getLastLeaves(masksGroup, ['group']); 31 | var i, len = leaves.length; 32 | for(i = 0; i < len; i += 1) { 33 | this.createNodeInstance(leaves[i], groupName + naming.GROUP_NAME + '_' + i); 34 | } 35 | } else { 36 | gr = node.createNode('group', groupName); 37 | this.createNodeInstance(gr, groupName); 38 | } 39 | var parentNode = gr; 40 | if(state.layerData.ks){ 41 | var transformArray = createTransformGroup(groupName, state.layerData.ks, state.timeOffset, parentNode); 42 | parentNode = node.nestArray(transformArray); 43 | var canReuse = false; //Todo find out if parent has not animated properties to reuse 44 | parentNode = factoryInstance.buildParenting(state.layerData.parent, parentNode, groupName, canReuse); 45 | parentNode = clipTimeLimits(parentNode, node.getAttribute(parentNode, 'android:name'), parentWorkAreaOffset); 46 | } 47 | return parentNode; 48 | } 49 | 50 | function clipTimeLimits(group, name, parentWorkAreaOffset) { 51 | var inPoint = state.globalInPoint; 52 | var outPoint = state.globalOutPoint + state.timeOffset; 53 | var layerData = state.layerData; 54 | var animatedProp; 55 | var timeCap = property.getTimeCap(); 56 | if(layerData.ip + state.timeOffset > 0 || layerData.op + state.timeOffset + parentWorkAreaOffset < timeCap) { 57 | if(targets.getTargetByNameAndProperty(name,'scaleY')) { 58 | name += naming.TIME_NAME; 59 | var timeGroup = node.createNode('group', name); 60 | node.nestChild(timeGroup, group); 61 | group = timeGroup; 62 | } 63 | var scaleX = (node.getAttribute(group, 'android:scaleX') || 1) * 100; 64 | var scaleY = (node.getAttribute(group, 'android:scaleY') || 1) * 100; 65 | var clipStart = layerData.ip + state.timeOffset > 0; 66 | var clipEnd = layerData.op + state.timeOffset + parentWorkAreaOffset < timeCap; 67 | if(clipStart) { 68 | node.addAttribute(group,'android:scaleY', 0); 69 | } 70 | if(clipStart || clipEnd) { 71 | animatedProp = property.createAnimatedProperty(name, 'scaleY', [{s:[0,0,100],e:[scaleX,scaleY,100],t:0},{t:0}], layerData.ip + state.timeOffset); 72 | targets.addTarget(animatedProp); 73 | } 74 | if(clipEnd) { 75 | animatedProp = property.createAnimatedProperty(name, 'scaleY', [{s:[scaleX,scaleY,100],e:[0,0,100],t:0},{t:0}], layerData.op + state.timeOffset + parentWorkAreaOffset); 76 | targets.addTarget(animatedProp); 77 | } 78 | } 79 | return group; 80 | } 81 | 82 | var factoryInstance = { 83 | setTimeOffset: setTimeOffset, 84 | setWorkAreaOffset: setWorkAreaOffset, 85 | exportNode: exportNode 86 | } 87 | return Object.assign(factoryInstance, masker(state), transformer(state)); 88 | } 89 | 90 | module.exports = layer; -------------------------------------------------------------------------------- /src/layers/shape/shape.js: -------------------------------------------------------------------------------- 1 | var layer = require ('../layer'); 2 | var drawableFactory = require ('./drawable'); 3 | var node = require ('../../node'); 4 | var naming = require('../../naming'); 5 | 6 | function shape(layerData, _level) { 7 | 8 | var drawables = []; 9 | var transforms = []; 10 | var level = _level || 0; 11 | var trimPath; 12 | 13 | var state = { 14 | shapes: layerData.shapes || layerData.it, 15 | layerData: layerData 16 | } 17 | 18 | function createNodeInstance(grouper, groupName){ 19 | var drawableNodes; 20 | var i, len = drawables.length; 21 | var j, jLen; 22 | for(i = 0; i < len; i += 1) { 23 | drawableNodes = drawables[i].exportDrawables(groupName + naming.DRAWABLE_NAME + '_' + i, state.timeOffset); 24 | jLen = drawableNodes.length; 25 | for(j = 0; j < jLen; j += 1) { 26 | node.nestChild(grouper, drawableNodes[j]); 27 | } 28 | } 29 | } 30 | 31 | function addPathToDrawables(path) { 32 | var i, len = drawables.length; 33 | for(i = 0; i < len; i += 1) { 34 | drawables[i].addPath(path, transforms, level, trimPath); 35 | } 36 | } 37 | 38 | function addEllipseToDrawables(shapeData) { 39 | var i, len = drawables.length; 40 | for(i = 0; i < len; i += 1) { 41 | drawables[i].addEllipse(shapeData, transforms, level, trimPath); 42 | } 43 | } 44 | 45 | function addRectToDrawables(shapeData) { 46 | var i, len = drawables.length; 47 | for(i = 0; i < len; i += 1) { 48 | drawables[i].addRectangle(shapeData, transforms, level, trimPath); 49 | } 50 | } 51 | 52 | function processData() { 53 | var i, len = state.shapes.length; 54 | var shapeGroup, drawable; 55 | var localDrawables = []; 56 | for (i = len - 1; i >= 0; i -= 1) { 57 | if(state.shapes[i].ty === 'gr') { 58 | shapeGroup = shape(state.shapes[i], level + 1); 59 | shapeGroup.setTimeOffset(state.timeOffset) 60 | .setDrawables(drawables) 61 | .setTransforms(transforms) 62 | .setTrimPath(trimPath) 63 | .processData(); 64 | } else if(state.shapes[i].ty === 'fl' || state.shapes[i].ty === 'st') { 65 | drawable = drawableFactory(state.shapes[i], level, state.timeOffset); 66 | drawables.push(drawable); 67 | localDrawables.push(drawable); 68 | } else if(state.shapes[i].ty === 'tr') { 69 | transforms.push(state.shapes[i]); 70 | } else if(state.shapes[i].ty === 'sh') { 71 | addPathToDrawables(state.shapes[i]); 72 | } else if(state.shapes[i].ty === 'el') { 73 | addEllipseToDrawables(state.shapes[i]); 74 | } else if(state.shapes[i].ty === 'rc') { 75 | addRectToDrawables(state.shapes[i]); 76 | } else if(state.shapes[i].ty === 'tm') { 77 | trimPath = state.shapes[i]; 78 | } else { 79 | //console.log(state.shapes[i].ty) 80 | } 81 | } 82 | 83 | len = localDrawables.length; 84 | for(i = 0; i < len; i += 1) { 85 | drawable = localDrawables[i]; 86 | drawable.close(); 87 | } 88 | return factoryInstance; 89 | } 90 | 91 | function setTrimPath(_trimPath) { 92 | trimPath = _trimPath; 93 | return factoryInstance; 94 | } 95 | 96 | function setDrawables(_drawables) { 97 | drawables = _drawables; 98 | return factoryInstance; 99 | } 100 | 101 | function setTransforms(_transforms) { 102 | var i, len = _transforms.length; 103 | for(i = 0; i < len; i += 1) { 104 | transforms.push(_transforms[i]); 105 | } 106 | return factoryInstance; 107 | } 108 | 109 | var factoryInstance = { 110 | setDrawables: setDrawables, 111 | setTransforms: setTransforms, 112 | setTrimPath: setTrimPath, 113 | processData: processData, 114 | createNodeInstance: createNodeInstance 115 | }; 116 | Object.assign(factoryInstance, layer(state)); 117 | 118 | return factoryInstance; 119 | } 120 | 121 | module.exports = shape; -------------------------------------------------------------------------------- /src/avd/avd.js: -------------------------------------------------------------------------------- 1 | var compositionFactory = require ('../layers/composition'); 2 | var node = require ('../node'); 3 | var naming = require ('../naming'); 4 | var property = require ('../property'); 5 | var targets = require ('../targets/targets'); 6 | 7 | function avd(_animationData) { 8 | 9 | var attributes = [{ 10 | key: 'xmlns:android', 11 | value: 'http://schemas.android.com/apk/res/android' 12 | },{ 13 | key: 'xmlns:aapt', 14 | value: 'http://schemas.android.com/aapt' 15 | }]; 16 | 17 | var _composition, animationData; 18 | 19 | function createVectorDrawable(width, height) { 20 | var attributes = [{ 21 | key: 'android:height', 22 | value: height + 'dp' 23 | },{ 24 | key: 'android:width', 25 | value: width + 'dp' 26 | },{ 27 | key: 'android:viewportHeight', 28 | value: height 29 | },{ 30 | key: 'android:viewportWidth', 31 | value: width 32 | }]; 33 | var nodeElement = node.createNodeWithAttributes('vector', attributes, ''); 34 | return nodeElement; 35 | } 36 | 37 | function createAAPTVectorDrawable() { 38 | var attributes = [{ 39 | key: 'name', 40 | value: 'android:drawable' 41 | }]; 42 | var nodeElement = node.createNodeWithAttributes('aapt:attr', attributes, ''); 43 | return nodeElement; 44 | // 45 | } 46 | 47 | function createTimeRangeObject() { 48 | var name = 'time_group'; 49 | var timeNode = node.createNode('group',name); 50 | var attributes = [{ 51 | key: 'android:propertyName', 52 | value: 'translateX' 53 | }, 54 | { 55 | key: 'android:duration', 56 | value: Math.round((animationData.op - animationData.ip)/animationData.fr*1000) 57 | }, 58 | { 59 | key: 'android:startOffset', 60 | value: '0' 61 | }, 62 | { 63 | key: 'android:valueFrom', 64 | value: '0' 65 | }, 66 | { 67 | key: 'android:valueTo', 68 | value: '1' 69 | }, 70 | { 71 | key: 'android:valueType', 72 | value: 'floatType' 73 | }]; 74 | var objectAnimator = node.createNodeWithAttributes('objectAnimator', attributes, ''); 75 | var target = property.createTargetNode(name); 76 | var aapt = property.createAAPTAnimation(); 77 | node.nestChild(target, aapt); 78 | var set = property.createSetNode(); 79 | node.nestChild(aapt, set); 80 | node.nestChild(set, objectAnimator); 81 | targets.addTarget(target); 82 | return timeNode; 83 | } 84 | 85 | 86 | function exportNode() { 87 | var promise = new Promise(function(resolve, reject){ 88 | targets.resetTargets(); 89 | var avdElem = node.createNodeWithAttributes('animated-vector', attributes); 90 | var aaptVectorElem = createAAPTVectorDrawable(); 91 | var vectorElem = createVectorDrawable(animationData.w, animationData.h); 92 | node.nestChild(aaptVectorElem, vectorElem); 93 | node.nestChild(avdElem, aaptVectorElem); 94 | node.nestChild(vectorElem, _composition.exportNode(naming.ROOT_NAME)); 95 | node.nestChild(vectorElem, createTimeRangeObject()) 96 | targets.buildTargets(avdElem); 97 | resolve(avdElem); 98 | }) 99 | return promise; 100 | } 101 | 102 | function createTargets() { 103 | var targets = []; 104 | _composition.createTargets(targets); 105 | } 106 | 107 | function processAnimation(_animationData) { 108 | var promise = new Promise(function(resolve, reject){ 109 | animationData = _animationData; 110 | property.setFrameRate(animationData.fr); 111 | property.setTimeCap(animationData.op); 112 | _composition = compositionFactory(_animationData, _animationData.assets) 113 | .setTimeOffset(-_animationData.ip) 114 | .setWorkAreaOffset(_animationData.ip) 115 | .processData(); 116 | resolve(); 117 | }) 118 | return promise; 119 | } 120 | 121 | return { 122 | exportNode: exportNode, 123 | createTargets: createTargets, 124 | processAnimation: processAnimation 125 | } 126 | } 127 | 128 | module.exports = avd; -------------------------------------------------------------------------------- /src/node.js: -------------------------------------------------------------------------------- 1 | function createNodeWithAttributes(tagName, attributes, name) { 2 | var node = createNode(tagName, name); 3 | var i, len = attributes.length; 4 | for(i = 0; i < len; i += 1) { 5 | addAttribute(node, attributes[i].key, attributes[i].value); 6 | } 7 | return node; 8 | } 9 | 10 | function isArray(element) { 11 | var what = Object.prototype.toString; 12 | return what.call(element) === '[object Array]'; 13 | } 14 | 15 | function createNode(tagName, name) { 16 | var node = {}; 17 | Object.defineProperty(node, tagName, { 18 | value: { _attr: {} }, 19 | writable: true, 20 | enumerable: true, 21 | configurable: true 22 | }); 23 | if(name) { 24 | addAttribute(node, 'android:name', name); 25 | } 26 | return node; 27 | } 28 | 29 | function addAttribute(object, key, value) { 30 | var tagName = getTagName(object); 31 | var children = getChildren(object); 32 | if(isArray(children)){ 33 | var i = 0, len = children.length; 34 | var attrsContainer; 35 | while(i < len) { 36 | if(children[i]._attr) { 37 | attrsContainer = children[i]; 38 | break; 39 | } 40 | i += 1; 41 | } 42 | } else { 43 | attrsContainer = children; 44 | } 45 | 46 | if (!attrsContainer) { 47 | attrsContainer = {_attr:{}}; 48 | object[tagName] = attrsContainer; 49 | } 50 | attrsContainer._attr[key] = value; 51 | } 52 | 53 | function getTagName(nodeElem) { 54 | var keys = Object.keys(nodeElem); 55 | return keys[0]; 56 | } 57 | 58 | function getAttribute(nodeElem, key) { 59 | var children = getChildren(nodeElem); 60 | if(isArray(children)){ 61 | var i =0, len = children.length; 62 | while(i < len) { 63 | if(children[i]._attr && children[i]._attr[key]) { 64 | return children[i]._attr[key]; 65 | } 66 | i += 1; 67 | } 68 | } else if(children._attr && children._attr[key]) { 69 | return children._attr[key]; 70 | } 71 | return ''; 72 | } 73 | 74 | function getChildren(nodeElem) { 75 | var nodeTagName = getTagName(nodeElem); 76 | var children = nodeElem[nodeTagName]; 77 | return children; 78 | } 79 | 80 | function getChild(nodeElem, childName) { 81 | var children = getChildren(nodeElem); 82 | if(isArray(children)){ 83 | var i =0, len = children.length, tagName; 84 | while(i < len) { 85 | tagName = getTagName(children[i]); 86 | if(tagName === childName) { 87 | return children[i]; 88 | } 89 | i += 1; 90 | } 91 | } 92 | return ''; 93 | } 94 | 95 | function nestChild(nodeElem, nested) { 96 | if(!nested) { 97 | return; 98 | } 99 | var tagName = getTagName(nodeElem); 100 | if(!isArray(nodeElem[tagName])){ 101 | var attrs = nodeElem[tagName]; 102 | nodeElem[tagName] = [attrs]; 103 | } 104 | nodeElem[tagName].push(nested); 105 | } 106 | 107 | function nestChildAt(nodeElem, nested, pos) { 108 | if(!nested) { 109 | return; 110 | } 111 | var tagName = getTagName(nodeElem); 112 | if(!isArray(nodeElem[tagName])){ 113 | var attrs = nodeElem[tagName]; 114 | nodeElem[tagName] = [attrs]; 115 | } 116 | nodeElem[tagName].splice(pos,0,nested); 117 | } 118 | 119 | function cloneNode(node, targets, suffix) { 120 | var cloningNode = JSON.parse(JSON.stringify(node)); 121 | renameNode(cloningNode, targets, suffix); 122 | return cloningNode; 123 | } 124 | 125 | function renameNode(nodeElem, targets, suffix) { 126 | var children = getChildren(nodeElem); 127 | if(children && isArray(children)) { 128 | var i, len = children.length; 129 | for( i = 0; i < len; i += 1) { 130 | renameNode(children[i], targets, suffix); 131 | } 132 | } 133 | var androidName = getAttribute(nodeElem, 'android:name'); 134 | if(androidName) { 135 | duplicateTargets(targets, androidName, androidName + suffix); 136 | addAttribute(nodeElem, 'android:name', androidName + suffix); 137 | } 138 | } 139 | 140 | function duplicateTargets(targets, name, newName) { 141 | var i, len = targets.length, newTarget; 142 | for( i = 0 ; i < len; i += 1) { 143 | if(targets[i].target[0]._attr['android:name'] === name) { 144 | newTarget = JSON.parse(JSON.stringify(targets[i])); 145 | newTarget.target[0]._attr['android:name'] = newName; 146 | targets.push(newTarget); 147 | } 148 | } 149 | } 150 | 151 | function nestArray(array) { 152 | var i, len = array.length; 153 | for(i = 1; i < len; i += 1) { 154 | nestChild(array[i],array[i - 1]); 155 | } 156 | return array[array.length - 1]; 157 | } 158 | 159 | function getLastLeaves(node, leaveTypes) { 160 | var leaves = []; 161 | var children = getChildren(node); 162 | var hasChildren = false; 163 | if(children && isArray(children)) { 164 | var i, len = children.length, tagName; 165 | for(i = 0; i < len; i += 1) { 166 | tagName = getTagName(children[i]); 167 | if (tagName !== '_attr' && (!leaveTypes || leaveTypes.indexOf(tagName) !== -1)) { 168 | hasChildren = true; 169 | leaves = leaves.concat(getLastLeaves(children[i], leaveTypes)); 170 | } 171 | } 172 | } 173 | if(!hasChildren) { 174 | leaves.push(node); 175 | } 176 | return leaves; 177 | } 178 | 179 | module.exports = { 180 | createNode: createNode, 181 | createNodeWithAttributes: createNodeWithAttributes, 182 | addAttribute: addAttribute, 183 | getTagName: getTagName, 184 | getAttribute: getAttribute, 185 | nestChild: nestChild, 186 | nestChildAt: nestChildAt, 187 | nestArray: nestArray, 188 | getChild: getChild, 189 | getChildren: getChildren, 190 | getLastLeaves: getLastLeaves, 191 | cloneNode: cloneNode 192 | } -------------------------------------------------------------------------------- /src/layers/masker.js: -------------------------------------------------------------------------------- 1 | var naming = require ('../naming'); 2 | var node = require ('../node'); 3 | var targets = require ('../targets/targets'); 4 | var property = require ('../property'); 5 | var createPathData = require ('../pathData'); 6 | 7 | function masker(state) { 8 | var masks = []; 9 | var maskCount = 0, nestCount = 0; 10 | var hasAnimatedProp = false; 11 | var currentMaskData = { 12 | type:'', 13 | currentPaths: [] 14 | }; 15 | var clipName,containerGroup,animatedProp; 16 | var clipPathString = ''; 17 | var masksList = []; 18 | 19 | var buildMask = (function() { 20 | var prevType = ''; 21 | return function(path) { 22 | if(!path) { 23 | return; 24 | } 25 | var groupContainerNode = node.createNode('group', clipName + naming.GROUP_NAME); 26 | var clipPath = node.createNode('clip-path', clipName); 27 | var groupNode = node.createNode('group', clipName + naming.GROUP_NAME + naming.GROUP_NAME); 28 | node.nestChild(groupContainerNode, clipPath); 29 | node.addAttribute(clipPath,'android:pathData', path); 30 | if (currentMaskData.type === 'i') { 31 | if(masksList.length) { 32 | // We can add intersecting masks as siblings instead of nesting one inside the other 33 | if (prevType === 'i') { 34 | var currentContainer = masksList[masksList.length -1].container; 35 | var index = node.getChildren(currentContainer).length - 1; 36 | node.nestChildAt(currentContainer, clipPath, index); 37 | groupContainerNode = currentContainer; 38 | } else { 39 | var i, len = masksList.length; 40 | for(i = 0; i < len; i += 1) { 41 | node.nestChild(groupContainerNode, masksList[i].container); 42 | } 43 | } 44 | 45 | } else { 46 | node.nestChild(groupContainerNode, groupNode); 47 | } 48 | masksList.length = 0; 49 | } else if (currentMaskData.type === 'a') { 50 | node.nestChild(groupContainerNode, groupNode); 51 | } 52 | masksList.push({ 53 | container: groupContainerNode 54 | }) 55 | 56 | animatedProp = null; 57 | nestCount = 0; 58 | clipPathString = ''; 59 | prevType = currentMaskData.type; 60 | } 61 | }()) 62 | 63 | function buildPreviousMaskGroup(name){ 64 | if(!currentMaskData.type){ 65 | return; 66 | } 67 | if(!containerGroup){ 68 | containerGroup = node.createNode('group', name + naming.GROUP_NAME); 69 | } 70 | var paths = currentMaskData.currentPaths; 71 | var i, len = paths.length, j, jLen; 72 | var currentClipPathString = ''; 73 | var animatedProp, prevNode, maskNode; 74 | clipName = name + naming.CLIP_NAME + '_' + maskCount; 75 | for (i = 0; i < len; i+= 1) { 76 | if (paths[i].type === 'i') { 77 | if (paths[i].pt.a === 1) { 78 | animatedProp = property.createAnimatedPathData(clipName, paths[i].pt.k, null, clipPathString, state.timeOffset); 79 | targets.addTarget(animatedProp); 80 | clipPathString += ' ' + createPathData(paths[i].pt.k[0].s[0], null); 81 | } else { 82 | clipPathString += ' ' + createPathData(paths[i].pt.k, null); 83 | } 84 | } else if (paths[i].type === 'a') { 85 | if (paths[i].pt.a === 1) { 86 | animatedProp = property.createAnimatedPathData(clipName, paths[i].pt.k, null, clipPathString, state.timeOffset); 87 | targets.addTarget(animatedProp); 88 | clipPathString += ' ' + createPathData(paths[i].pt.k[0].s[0], null); 89 | } else { 90 | currentClipPathString = createPathData(paths[i].pt.k, null); 91 | if(animatedProp) { 92 | var aaptAttr = node.getChild(animatedProp,'aapt:attr'); 93 | var setProp = node.getChild(aaptAttr,'set'); 94 | var setChildren = node.getChildren(setProp); 95 | jLen = setChildren.length; 96 | var objectAnimator, value; 97 | for(j = 0; j < jLen; j += 1) { 98 | value = node.getAttribute(setChildren[j],'android:valueFrom'); 99 | if(value) { 100 | node.addAttribute(setChildren[j],'android:valueFrom', value + currentClipPathString); 101 | value = node.getAttribute(setChildren[j],'android:valueTo'); 102 | node.addAttribute(setChildren[j],'android:valueTo', value + currentClipPathString); 103 | } 104 | } 105 | } 106 | clipPathString += ' ' + currentClipPathString; 107 | } 108 | } 109 | } 110 | buildMask(clipPathString); 111 | currentMaskData.type = ''; 112 | currentMaskData.currentPaths.length = 0; 113 | hasAnimatedProp = false; 114 | maskCount += 1; 115 | } 116 | 117 | function getMasks(name) { 118 | var masksProperties = state.layerData.masksProperties; 119 | if(masksProperties) { 120 | var i, len = masksProperties.length, maskProp; 121 | for (i = 0; i < len; i += 1) { 122 | maskProp = masksProperties[i]; 123 | if (!maskProp.inv) { 124 | if (maskProp.mode === 'a') { 125 | if(currentMaskData.type !== 'a' && currentMaskData.type !== '' || (maskProp.pt.a === 1 && hasAnimatedProp)){ 126 | buildPreviousMaskGroup(name); 127 | } 128 | currentMaskData.type = 'a'; 129 | if (maskProp.pt.a === 1) { 130 | hasAnimatedProp = true; 131 | } 132 | currentMaskData.currentPaths.push({pt:maskProp.pt, type:'a'}); 133 | } else if (maskProp.mode === 'i') { 134 | if(currentMaskData.type !== ''){ 135 | buildPreviousMaskGroup(name); 136 | } 137 | currentMaskData.type = 'i'; 138 | currentMaskData.currentPaths.push({pt:maskProp.pt, type:'i'}); 139 | } 140 | } 141 | } 142 | buildPreviousMaskGroup(name); 143 | if(masksList.length) { 144 | len = masksList.length; 145 | for (i = 0; i < len; i += 1) { 146 | // node.nestChild(containerGroup, masksList[i].clip); 147 | // node.nestChild(containerGroup, masksList[i].group); 148 | node.nestChild(containerGroup, masksList[i].container); 149 | } 150 | } 151 | } 152 | return containerGroup; 153 | } 154 | 155 | return { 156 | getMasks: getMasks 157 | } 158 | } 159 | 160 | module.exports = masker; -------------------------------------------------------------------------------- /src/helpers/transform/createTransformGroup.js: -------------------------------------------------------------------------------- 1 | var node = require('../../node'); 2 | var property = require('../../property'); 3 | var targets = require('../../targets/targets'); 4 | var naming = require('../../naming'); 5 | 6 | function isPositionAnimated(positionProperty) { 7 | return isPositionXAnimated(positionProperty) || isPositionYAnimated(positionProperty); 8 | } 9 | 10 | function isPositionXAnimated(positionProperty) { 11 | if(positionProperty.s && positionProperty.x.a === 0) { 12 | return false; 13 | } else if(!positionProperty.s && positionProperty.a === 0) { 14 | return false; 15 | } 16 | return true; 17 | } 18 | 19 | function isPositionYAnimated(positionProperty) { 20 | if(positionProperty.s && positionProperty.y.a === 0) { 21 | return false; 22 | } else if(!positionProperty.s && positionProperty.a === 0) { 23 | return false; 24 | } 25 | return true; 26 | } 27 | 28 | function getPositionX(positionProperty) { 29 | if(positionProperty.s) { 30 | return positionProperty.x.k; 31 | } else { 32 | return positionProperty.k[0]; 33 | } 34 | } 35 | 36 | function getPositionY(positionProperty) { 37 | if(positionProperty.s) { 38 | return positionProperty.y.k; 39 | } else { 40 | return positionProperty.k[1]; 41 | } 42 | } 43 | 44 | function createTransformGroup(name, transform, timeOffset, _container) { 45 | var changed = false; 46 | var nodes =[]; 47 | var currentName = name; 48 | var container; 49 | var positionX, positionY; 50 | var animatedProperty; 51 | //var name = node.getAttribute(container, 'android:name'); 52 | if(_container) { 53 | container = _container; 54 | nodes.push(container); 55 | } 56 | function addAttributeToContainer(key, value) { 57 | if(!container) { 58 | currentName = name + naming.TRANSFORM_NAME + '_' + + nodes.length; 59 | container = node.createNode('group', currentName); 60 | nodes.push(container); 61 | } 62 | node.addAttribute(container,key, value); 63 | return container; 64 | } 65 | if(!isPositionAnimated(transform.p) && transform.a.a === 0) { 66 | positionX = getPositionX(transform.p); 67 | positionY = getPositionY(transform.p); 68 | if(positionX - transform.a.k[0] !== 0) { 69 | addAttributeToContainer('android:translateX', positionX - transform.a.k[0]); 70 | //node.addAttribute(container,'android:translateX', positionX - transform.a.k[0]); 71 | } 72 | if(positionY - transform.a.k[1] !== 0) { 73 | addAttributeToContainer('android:translateY', positionY - transform.a.k[1]); 74 | //node.addAttribute(container,'android:translateY', positionY - transform.a.k[1]); 75 | } 76 | if(transform.r.a === 1 || transform.r.k !== 0 || transform.s.a === 1 || transform.s.k[0] !== 100 || transform.s.k[1] !== 100) { 77 | if(transform.a.k[0] !== 0) { 78 | addAttributeToContainer('android:pivotX', transform.a.k[0]); 79 | //node.addAttribute(container,'android:pivotX', transform.a.k[0]); 80 | } 81 | if(transform.a.k[1] !== 0) { 82 | addAttributeToContainer('android:pivotY', transform.a.k[1]); 83 | //node.addAttribute(container,'android:pivotY', transform.a.k[1]); 84 | } 85 | if(transform.r.a === 1 || transform.r.k !== 0) { 86 | if(transform.r.a === 0) { 87 | if(transform.r.k !== 0) { 88 | addAttributeToContainer('android:rotation', transform.r.k); 89 | //node.addAttribute(container,'android:rotation', transform.r.k); 90 | } 91 | } else { 92 | addAttributeToContainer('android:rotation', transform.r.k[0].s); 93 | //node.addAttribute(container,'android:rotation', transform.r.k[0].s); 94 | animatedProperty = property.createAnimatedProperty(currentName, 'rotation', transform.r.k, timeOffset); 95 | targets.addTarget(animatedProperty); 96 | } 97 | } 98 | if(transform.s.a === 1 || transform.s.k[0] !== 100 || transform.s.k[1] !== 100) { 99 | if(transform.s.a === 0) { 100 | if(transform.s.k[0] !== 100) { 101 | //node.addAttribute(container,'android:scaleX', transform.s.k[0]/100); 102 | addAttributeToContainer('android:scaleX', transform.s.k[0]/100); 103 | } 104 | if(transform.s.k[1] !== 100) { 105 | //node.addAttribute(container,'android:scaleY', transform.s.k[1]/100); 106 | addAttributeToContainer('android:scaleY', transform.s.k[1]/100); 107 | } 108 | }else { 109 | //node.addAttribute(container,'android:scaleX', transform.s.k[0].s[0]/100); 110 | //node.addAttribute(container,'android:scaleY', transform.s.k[0].s[1]/100); 111 | addAttributeToContainer('android:scaleX', transform.s.k[0].s[0]/100); 112 | addAttributeToContainer('android:scaleY', transform.s.k[0].s[1]/100); 113 | animatedProperty = property.createAnimatedProperty(currentName, 'scale', transform.s.k, timeOffset); 114 | targets.addTarget(animatedProperty); 115 | } 116 | } 117 | } 118 | } else { 119 | if(transform.a.a !== 0 || transform.a.k[0] !== 0 || transform.a.k[1] !== 0) { 120 | if (transform.a.a === 1) { 121 | animatedProperty = property.createAnimatedProperty(currentName, 'anchor', transform.a.k, timeOffset); 122 | targets.addTarget(animatedProperty); 123 | //node.addAttribute(container,'android:translateX', -transform.a.k[0].s[0]); 124 | //node.addAttribute(container,'android:translateY', -transform.a.k[0].s[1]); 125 | addAttributeToContainer('android:translateX', -transform.a.k[0].s[0]); 126 | addAttributeToContainer('android:translateY', -transform.a.k[0].s[1]); 127 | } else if(transform.a.k[0] !== 0 || transform.a.k[1] !== 0) { 128 | if(transform.a.k[0] !== 0) { 129 | //node.addAttribute(container,'android:translateX', -transform.a.k[0]); 130 | addAttributeToContainer('android:translateX', -transform.a.k[0]); 131 | } 132 | if(transform.a.k[1] !== 0) { 133 | //node.addAttribute(container,'android:translateY', -transform.a.k[1]); 134 | addAttributeToContainer('android:translateY', -transform.a.k[1]); 135 | } 136 | } 137 | //var anchorGroupName = name + '_pivot'; 138 | container = null; 139 | //var anchorContainer = node.createNode('group', anchorGroupName); 140 | //node.nestChild(anchorContainer, container); 141 | //container = anchorContainer; 142 | //name = anchorGroupName; 143 | } 144 | if(transform.p){ 145 | var positionXAnimatedFlag = isPositionXAnimated(transform.p); 146 | var positionYAnimatedFlag = isPositionYAnimated(transform.p); 147 | if(!positionXAnimatedFlag && !positionYAnimatedFlag) { 148 | positionX = getPositionX(transform.p); 149 | positionY = getPositionY(transform.p); 150 | if(positionX !== 0) { 151 | //node.addAttribute(container,'android:translateX', positionX); 152 | addAttributeToContainer('android:translateX', positionX); 153 | } 154 | if(positionY !== 0) { 155 | //node.addAttribute(container,'android:translateY', transform.p.k[1]); 156 | addAttributeToContainer('android:translateY', positionY); 157 | } 158 | } else if(!transform.p.s){ 159 | addAttributeToContainer('android:translateX', transform.p.k[0].s[0]); 160 | addAttributeToContainer('android:translateY', transform.p.k[0].s[1]); 161 | animatedProperty = property.createAnimatedProperty(currentName, 'position', transform.p.k, timeOffset); 162 | targets.addTarget(animatedProperty); 163 | } else { 164 | if (!positionXAnimatedFlag) { 165 | positionX = getPositionX(transform.p); 166 | if(positionX !== 0) { 167 | addAttributeToContainer('android:translateX', positionX); 168 | } 169 | } else { 170 | addAttributeToContainer('android:translateX', transform.p.x.k[0].s); 171 | animatedProperty = property.createAnimatedProperty(currentName, 'positionX', transform.p.x.k, timeOffset); 172 | targets.addTarget(animatedProperty); 173 | } 174 | if (!positionYAnimatedFlag) { 175 | positionY = getPositionY(transform.p); 176 | if(positionY !== 0) { 177 | addAttributeToContainer('android:translateY', positionY); 178 | } 179 | } else { 180 | addAttributeToContainer('android:translateY', transform.p.y.k[0].s); 181 | animatedProperty = property.createAnimatedProperty(currentName, 'positionY', transform.p.y.k, timeOffset); 182 | targets.addTarget(animatedProperty); 183 | } 184 | } 185 | 186 | } 187 | if(transform.s.a === 0) { 188 | if(transform.s.k[0] !== 100) { 189 | //node.addAttribute(container,'android:scaleX', transform.s.k[0]/100); 190 | addAttributeToContainer('android:scaleX', transform.s.k[0]/100); 191 | } 192 | if(transform.s.k[1] !== 100) { 193 | //node.addAttribute(container,'android:scaleY', transform.s.k[1]/100); 194 | addAttributeToContainer('android:scaleY', transform.s.k[1]/100); 195 | } 196 | }else { 197 | //node.addAttribute(container,'android:scaleX', transform.s.k[0].s[0]/100); 198 | //node.addAttribute(container,'android:scaleY', transform.s.k[0].s[1]/100); 199 | addAttributeToContainer('android:scaleX', transform.s.k[0].s[0]/100); 200 | addAttributeToContainer('android:scaleY', transform.s.k[0].s[1]/100); 201 | animatedProperty = property.createAnimatedProperty(currentName, 'scale', transform.s.k, timeOffset); 202 | targets.addTarget(animatedProperty); 203 | } 204 | if(transform.r.a === 0) { 205 | if(transform.r.k !== 0) { 206 | //node.addAttribute(container,'android:rotation', transform.r.k); 207 | addAttributeToContainer('android:rotation', transform.r.k); 208 | } 209 | } else { 210 | //node.addAttribute(container,'android:rotation', transform.r.k[0].s); 211 | addAttributeToContainer('android:rotation', transform.r.k[0].s); 212 | animatedProperty = property.createAnimatedProperty(currentName, 'rotation', transform.r.k, timeOffset); 213 | targets.addTarget(animatedProperty); 214 | } 215 | } 216 | return nodes; 217 | } 218 | 219 | module.exports = createTransformGroup; -------------------------------------------------------------------------------- /src/property.js: -------------------------------------------------------------------------------- 1 | var node = require('./node'); 2 | var createPathData = require('./pathData'); 3 | var rgbHex = require('./helpers/rgbToHex'); 4 | var Matrix = require('transformatrix'); 5 | 6 | var _matrix = new Matrix(); 7 | var frameRate = 0; 8 | var timeCap = Number.MAX_SAFE_INTEGER; 9 | 10 | function createAnimatedProperty(targetName, propertyType, keyframes, timeOffset) { 11 | var target = createTargetNode(targetName); 12 | var aapt = createAAPTAnimation(); 13 | node.nestChild(target, aapt); 14 | var set = createSetNode(); 15 | node.nestChild(aapt, set); 16 | if(keyframes[0].t > 0) { 17 | var extraKeyframe = JSON.parse(JSON.stringify(keyframes[0])); 18 | extraKeyframe.e = extraKeyframe.s; 19 | extraKeyframe.t = 0; 20 | keyframes.splice(0,0,extraKeyframe); 21 | } 22 | var i, len = keyframes.length; 23 | var objectAnimator, multiplier; 24 | var index; 25 | for( i = 1; i < len; i += 1) { 26 | if(propertyType === 'position') { 27 | if (keyframes[i - 1].to) { 28 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'translateXY', {type:'combined', interpolationType:'unidimensional', timeOffset: timeOffset}); 29 | node.nestChild(set, objectAnimator); 30 | } else { 31 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'translateX', {type:'multidimensional', index:0, interpolationType:'unidimensional', timeOffset: timeOffset}); 32 | node.nestChild(set, objectAnimator); 33 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'translateY', {type:'multidimensional', index:1, interpolationType:'unidimensional', timeOffset: timeOffset}); 34 | node.nestChild(set, objectAnimator); 35 | } 36 | 37 | } else if(propertyType === 'positionX' || propertyType === 'positionY') { 38 | var propertyName = propertyType === 'positionX' ? 'translateX' : 'translateY'; 39 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], propertyName, {type:'unidimensional', interpolationType:'unidimensional', timeOffset: timeOffset}); 40 | node.nestChild(set, objectAnimator); 41 | 42 | } else if(propertyType === 'anchor') { 43 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'translateX', {type:'multidimensional', index:0, interpolationType:'unidimensional', multiplier:-1, timeOffset: timeOffset}); 44 | node.nestChild(set, objectAnimator); 45 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'translateY', {type:'multidimensional', index:1, interpolationType:'unidimensional', multiplier:-1, timeOffset: timeOffset}); 46 | node.nestChild(set, objectAnimator); 47 | } else if(propertyType === 'scale') { 48 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'scaleX', {type:'multidimensional', index:0, interpolationType:'multidimensional', multiplier:0.01, timeOffset: timeOffset}); 49 | node.nestChild(set, objectAnimator); 50 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'scaleY', {type:'multidimensional', index:1, interpolationType:'multidimensional', multiplier:0.01, timeOffset: timeOffset}); 51 | node.nestChild(set, objectAnimator); 52 | } else if(propertyType === 'scaleX' || propertyType === 'scaleY') { 53 | index = propertyType === 'scaleX' ? 0 : 1; 54 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], propertyType, {type:'multidimensional', index:index, interpolationType:'multidimensional', multiplier:0.01, timeOffset: timeOffset}); 55 | node.nestChild(set, objectAnimator); 56 | } else if(propertyType === 'rotation' || propertyType === 'strokeWidth') { 57 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], propertyType, {type:'unidimensional', index:1, interpolationType:'unidimensional', timeOffset: timeOffset}); 58 | node.nestChild(set, objectAnimator); 59 | } else if(propertyType === 'pathData') { 60 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'pathData', {type:'path', interpolationType:'unidimensional', timeOffset: timeOffset}); 61 | node.nestChild(set, objectAnimator); 62 | } else if(propertyType === 'fillColor' || propertyType === 'strokeColor') { 63 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], propertyType, {type:'color', interpolationType:'unidimensional', timeOffset: timeOffset}); 64 | node.nestChild(set, objectAnimator); 65 | } else if(propertyType === 'strokeAlpha' || propertyType === 'fillAlpha' || propertyType === 'trimPathEnd' || propertyType === 'trimPathStart' || propertyType === 'trimPathOffset') { 66 | multiplier = propertyType === 'trimPathOffset' ? 1/360 : 0.01; 67 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], propertyType, {type:'unidimensional', interpolationType:'unidimensional', multiplier:multiplier, timeOffset: timeOffset}); 68 | node.nestChild(set, objectAnimator); 69 | } 70 | } 71 | return target; 72 | } 73 | 74 | function createAnimatedPathData(targetName, keyframes, matrix, staticPath, timeOffset) { 75 | var target = createTargetNode(targetName); 76 | var aapt = createAAPTAnimation(); 77 | node.nestChild(target, aapt); 78 | var set = createSetNode(); 79 | node.nestChild(aapt, set); 80 | if(keyframes[0].t > 0) { 81 | var extraKeyframe = JSON.parse(JSON.stringify(keyframes[0])); 82 | extraKeyframe.e = extraKeyframe.s; 83 | extraKeyframe.t = 0; 84 | keyframes.splice(0,0,extraKeyframe); 85 | } 86 | var i, len = keyframes.length; 87 | var objectAnimator, multiplier; 88 | for( i = 1; i < len; i += 1) { 89 | objectAnimator = createAnimatorObject(keyframes[i - 1], keyframes[i], 'pathData', {type:'path', interpolationType:'unidimensional', timeOffset: timeOffset, matrix: matrix, staticPath: staticPath}); 90 | node.nestChild(set, objectAnimator); 91 | } 92 | return target; 93 | } 94 | 95 | function createSetNode() { 96 | var attributes = [{ 97 | key: 'android:ordering', 98 | value: 'together' 99 | }]; 100 | return node.createNodeWithAttributes('set', attributes, ''); 101 | } 102 | 103 | function createAAPTAnimation() { 104 | var attributes = [{ 105 | key: 'name', 106 | value: 'android:animation' 107 | }]; 108 | return node.createNodeWithAttributes('aapt:attr', attributes, ''); 109 | } 110 | 111 | function createTargetNode(nodeName) { 112 | //android:name="plus_group" 113 | var attributes = [{ 114 | key: 'android:name', 115 | value: nodeName 116 | }]; 117 | return node.createNodeWithAttributes('target', attributes, ''); 118 | } 119 | 120 | function createAnimatorObject(initialValue, finalValue, propertyName, options) { 121 | options.multiplier = options.multiplier || 1; 122 | options.timeOffset = options.timeOffset || 0; 123 | options.matrix = options.matrix || _matrix.reset(); 124 | options.staticPath = options.staticPath || ''; 125 | var duration = finalValue.t - initialValue.t; 126 | var startOffset = initialValue.t + options.timeOffset; 127 | if (options.timeOffset + finalValue.t > timeCap || startOffset < 0) { 128 | return null; 129 | } 130 | var attributes = [{ 131 | key: 'android:propertyName', 132 | value: propertyName 133 | }, 134 | { 135 | key: 'android:duration', 136 | value: Math.round(duration / frameRate * 1000) 137 | }, 138 | { 139 | key: 'android:startOffset', 140 | value: Math.round(startOffset / frameRate * 1000) 141 | }]; 142 | if (options.type === 'multidimensional') { 143 | attributes.push({ 144 | key: 'android:valueFrom', 145 | value: initialValue.s[options.index] * options.multiplier 146 | }) 147 | 148 | if(initialValue.h === 1) { 149 | attributes.push({ 150 | key: 'android:valueTo', 151 | value: initialValue.s[options.index] * options.multiplier 152 | }) 153 | } else if('e' in initialValue) { 154 | attributes.push({ 155 | key: 'android:valueTo', 156 | value: initialValue.e[options.index] * options.multiplier 157 | }) 158 | } else{ 159 | attributes.push({ 160 | key: 'android:valueTo', 161 | value: finalValue.s[options.index] * options.multiplier 162 | }) 163 | } 164 | attributes.push({ 165 | key: 'android:valueType', 166 | value: 'floatType' 167 | }) 168 | } else if (options.type === 'unidimensional') { 169 | attributes.push({ 170 | key: 'android:valueFrom', 171 | value: initialValue.s * options.multiplier 172 | }) 173 | if(initialValue.h === 1) { 174 | attributes.push({ 175 | key: 'android:valueTo', 176 | value: initialValue.s * options.multiplier 177 | }) 178 | } else if('e' in initialValue) { 179 | attributes.push({ 180 | key: 'android:valueTo', 181 | value: initialValue.e * options.multiplier 182 | }) 183 | } else { 184 | attributes.push({ 185 | key: 'android:valueTo', 186 | value: finalValue.s * options.multiplier 187 | }) 188 | } 189 | attributes.push({ 190 | key: 'android:valueType', 191 | value: 'floatType' 192 | }) 193 | } else if (options.type === 'path') { 194 | attributes.push({ 195 | key: 'android:valueFrom', 196 | value: options.staticPath + createPathData(initialValue.s[0], options.matrix) 197 | }) 198 | if(initialValue.h === 1) { 199 | attributes.push({ 200 | key: 'android:valueTo', 201 | value: options.staticPath + createPathData(initialValue.s[0], options.matrix) 202 | }) 203 | } else if('e' in initialValue) { 204 | attributes.push({ 205 | key: 'android:valueTo', 206 | value: options.staticPath + createPathData(initialValue.e[0], options.matrix) 207 | }) 208 | } else { 209 | attributes.push({ 210 | key: 'android:valueTo', 211 | value: options.staticPath + createPathData(finalValue.s[0], options.matrix) 212 | }) 213 | } 214 | attributes.push({ 215 | key: 'android:valueType', 216 | value: 'pathType' 217 | }) 218 | } else if (options.type === 'color') { 219 | attributes.push({ 220 | key: 'android:valueFrom', 221 | value: rgbHex(initialValue.s[0]*255, initialValue.s[1]*255, initialValue.s[2]*255) 222 | }) 223 | if(initialValue.h === 1) { 224 | attributes.push({ 225 | key: 'android:valueTo', 226 | value: rgbHex(initialValue.s[0]*255, initialValue.s[1]*255, initialValue.s[2]*255) 227 | }) 228 | } else if('e' in initialValue) { 229 | attributes.push({ 230 | key: 'android:valueTo', 231 | value: rgbHex(initialValue.e[0]*255, initialValue.e[1]*255, initialValue.e[2]*255) 232 | }) 233 | } else { 234 | attributes.push({ 235 | key: 'android:valueTo', 236 | value: rgbHex(finalValue.s[0]*255, finalValue.s[1]*255, finalValue.s[2]*255) 237 | }) 238 | } 239 | attributes.push({ 240 | key: 'android:valueType', 241 | value: 'colorType' 242 | }) 243 | } else if (options.type === 'combined') { 244 | attributes.push({ 245 | key: 'android:propertyXName', 246 | value: 'translateX' 247 | }) 248 | attributes.push({ 249 | key: 'android:propertyYName', 250 | value: 'translateY' 251 | }) 252 | var endValue = ('e' in initialValue) ? initialValue.e : finalValue.s 253 | var pathValue = 'M ' + initialValue.s[0] + ',' + initialValue.s[1]; 254 | pathValue += 'C ' + (initialValue.s[0] + initialValue.to[0]) + ',' + (initialValue.s[1] + initialValue.to[1]); 255 | pathValue += ' ' + (endValue[0] + initialValue.ti[0]) + ',' + (endValue[1] + initialValue.ti[1]); 256 | pathValue += ' ' + (endValue[0]) + ',' + (endValue[1]); 257 | attributes.push({ 258 | key: 'android:pathData', 259 | value: pathValue 260 | }) 261 | //android:pathData="M -8.0,0.0 c 1.33333,0.0 6.66667,0.0 8.0,0.0" 262 | } 263 | var objectAnimator = node.createNodeWithAttributes('objectAnimator', attributes, ''); 264 | if(initialValue.h !== 1) { 265 | var interpolator = buildInterpolator(initialValue, finalValue, options); 266 | node.nestChild(objectAnimator, interpolator); 267 | } 268 | return objectAnimator; 269 | } 270 | 271 | function buildInterpolator(initialValue, finalValue, options) { 272 | if(!initialValue.o){ 273 | return null; 274 | } 275 | var attributes = [{ 276 | key: 'name', 277 | value: 'android:interpolator' 278 | }]; 279 | var aaptInterpolator = node.createNodeWithAttributes('aapt:attr', attributes, ''); 280 | var interpolationValue = 'M 0.0,0.0'; 281 | var ox,oy,ix,iy; 282 | if(options.interpolationType === 'unidimensional') { 283 | ox = initialValue.o.x; 284 | oy = initialValue.o.y; 285 | ix = initialValue.i.x; 286 | iy = initialValue.i.y; 287 | } else if(options.interpolationType === 'multidimensional') { 288 | ox = initialValue.o.x[options.index]; 289 | oy = initialValue.o.y[options.index]; 290 | ix = initialValue.i.x[options.index]; 291 | iy = initialValue.i.y[options.index]; 292 | 293 | } 294 | interpolationValue += ' c' + ox + ',' + oy; 295 | interpolationValue += ' ' + ix + ',' + iy; 296 | interpolationValue += ' 1.0,1.0'; 297 | var pathAttributes = [{ 298 | key: 'android:pathData', 299 | value: interpolationValue 300 | }] 301 | var pathInterpolator = node.createNodeWithAttributes('pathInterpolator', pathAttributes, ''); 302 | node.nestChild(aaptInterpolator, pathInterpolator); 303 | return aaptInterpolator; 304 | } 305 | 306 | function setFrameRate(_frameRate) { 307 | frameRate = _frameRate; 308 | } 309 | 310 | function setTimeCap(_timeCap) { 311 | timeCap = _timeCap; 312 | } 313 | 314 | function getTimeCap() { 315 | return timeCap; 316 | } 317 | 318 | module.exports = { 319 | createAnimatedProperty: createAnimatedProperty, 320 | createAnimatedPathData: createAnimatedPathData, 321 | createAnimatorObject: createAnimatorObject, 322 | createAAPTAnimation: createAAPTAnimation, 323 | createTargetNode: createTargetNode, 324 | createSetNode: createSetNode, 325 | setFrameRate: setFrameRate, 326 | setTimeCap: setTimeCap, 327 | getTimeCap: getTimeCap 328 | } -------------------------------------------------------------------------------- /src/layers/shape/drawable.js: -------------------------------------------------------------------------------- 1 | var node = require ('../../node'); 2 | var property = require ('../../property'); 3 | var targets = require ('../../targets/targets'); 4 | var createTransformGroup = require ('../../helpers/transform/createTransformGroup'); 5 | var rgbHex = require('../../helpers/rgbToHex'); 6 | var Matrix = require('transformatrix'); 7 | var createPathData = require('../../pathData'); 8 | var naming = require('../../naming'); 9 | 10 | var matrix = new Matrix(); 11 | var degToRads = Math.PI/180; 12 | var roundCorner = 0.5519; 13 | 14 | function convertEllipseToPath(ellipseData) { 15 | if(ellipseData.s.a !== 0 || ellipseData.p.a !== 0) { 16 | return null; 17 | } 18 | var p0 = ellipseData.p.k[0], p1 = ellipseData.p.k[1], s0 = ellipseData.s.k[0]/2, s1 = ellipseData.s.k[1]/2; 19 | var vPoints = [[0,0],[0,0],[0,0],[0,0]]; 20 | var oPoints = [[0,0],[0,0],[0,0],[0,0]]; 21 | var iPoints = [[0,0],[0,0],[0,0],[0,0]]; 22 | if(ellipseData.d !== 3){ 23 | vPoints[0][0] = p0; 24 | vPoints[0][1] = p1-s1; 25 | vPoints[1][0] = p0 + s0; 26 | vPoints[1][1] = p1; 27 | vPoints[2][0] = p0; 28 | vPoints[2][1] = p1+s1; 29 | vPoints[3][0] = p0 - s0; 30 | vPoints[3][1] = p1; 31 | iPoints[0][0] = p0 - s0*roundCorner - vPoints[0][0]; 32 | iPoints[0][1] = p1 - s1 - vPoints[0][1]; 33 | iPoints[1][0] = p0 + s0 - vPoints[1][0]; 34 | iPoints[1][1] = p1 - s1*roundCorner - vPoints[1][1]; 35 | iPoints[2][0] = p0 + s0*roundCorner - vPoints[2][0]; 36 | iPoints[2][1] = p1 + s1 - vPoints[2][1]; 37 | iPoints[3][0] = p0 - s0 - vPoints[3][0]; 38 | iPoints[3][1] = p1 + s1*roundCorner - vPoints[3][1]; 39 | oPoints[0][0] = p0 + s0*roundCorner - vPoints[0][0]; 40 | oPoints[0][1] = p1 - s1 - vPoints[0][1]; 41 | oPoints[1][0] = p0 + s0 - vPoints[1][0]; 42 | oPoints[1][1] = p1 + s1*roundCorner - vPoints[1][1]; 43 | oPoints[2][0] = p0 - s0*roundCorner - vPoints[2][0]; 44 | oPoints[2][1] = p1 + s1 - vPoints[2][1]; 45 | oPoints[3][0] = p0 - s0 - vPoints[3][0]; 46 | oPoints[3][1] = p1 - s1*roundCorner - vPoints[3][1]; 47 | }else{ 48 | vPoints[0][0] = p0; 49 | vPoints[0][1] = p1-s1; 50 | vPoints[1][0] = p0 - s0; 51 | vPoints[1][1] = p1; 52 | vPoints[2][0] = p0; 53 | vPoints[2][1] = p1+s1; 54 | vPoints[3][0] = p0 + s0; 55 | vPoints[3][1] = p1; 56 | iPoints[0][0] = p0 + s0*cPoint - vPoints[0][0]; 57 | iPoints[0][1] = p1 - s1 - vPoints[0][1]; 58 | iPoints[1][0] = p0 - s0 - vPoints[1][0]; 59 | iPoints[1][1] = p1 - s1*cPoint - vPoints[1][1]; 60 | iPoints[2][0] = p0 - s0*cPoint - vPoints[2][0]; 61 | iPoints[2][1] = p1 + s1 - vPoints[2][1]; 62 | iPoints[3][0] = p0 + s0 - vPoints[3][0]; 63 | iPoints[3][1] = p1 + s1*cPoint - vPoints[3][1]; 64 | oPoints[0][0] = p0 - s0*cPoint - vPoints[0][0]; 65 | oPoints[0][1] = p1 - s1 - vPoints[0][1]; 66 | oPoints[1][0] = p0 - s0 - vPoints[1][0]; 67 | oPoints[1][1] = p1 + s1*cPoint - vPoints[1][1]; 68 | oPoints[2][0] = p0 + s0*cPoint - vPoints[2][0]; 69 | oPoints[2][1] = p1 + s1 - vPoints[2][1]; 70 | oPoints[3][0] = p0 + s0 - vPoints[3][0]; 71 | oPoints[3][1] = p1 - s1*cPoint - vPoints[3][1]; 72 | } 73 | var pathObject = { 74 | ks: { 75 | a:0, 76 | k: { 77 | i:iPoints, 78 | v:vPoints, 79 | o:oPoints, 80 | c: true 81 | } 82 | } 83 | } 84 | return pathObject; 85 | } 86 | 87 | function convertRectangleToPath(rectangleData) { 88 | if(rectangleData.s.a !== 0 || rectangleData.p.a !== 0 || rectangleData.r.a !== 0) { 89 | return null; 90 | } 91 | 92 | 93 | /*var p0 = this.p.v[0], p1 = this.p.v[1], v0 = this.s.v[0]/2, v1 = this.s.v[1]/2; 94 | var round = bm_min(v0,v1,this.r.v); 95 | var cPoint = round*(1-roundCorner); 96 | this.v._length = 0; 97 | 98 | if(this.d === 2 || this.d === 1) { 99 | this.v.setTripleAt(p0+v0, p1-v1+round,p0+v0, p1-v1+round,p0+v0,p1-v1+cPoint,0, true); 100 | this.v.setTripleAt(p0+v0, p1+v1-round,p0+v0, p1+v1-cPoint,p0+v0, p1+v1-round,1, true); 101 | if(round!== 0){ 102 | this.v.setTripleAt(p0+v0-round, p1+v1,p0+v0-round,p1+v1,p0+v0-cPoint,p1+v1,2, true); 103 | this.v.setTripleAt(p0-v0+round,p1+v1,p0-v0+cPoint,p1+v1,p0-v0+round,p1+v1,3, true); 104 | this.v.setTripleAt(p0-v0,p1+v1-round,p0-v0,p1+v1-round,p0-v0,p1+v1-cPoint,4, true); 105 | this.v.setTripleAt(p0-v0,p1-v1+round,p0-v0,p1-v1+cPoint,p0-v0,p1-v1+round,5, true); 106 | this.v.setTripleAt(p0-v0+round,p1-v1,p0-v0+round,p1-v1,p0-v0+cPoint,p1-v1,6, true); 107 | this.v.setTripleAt(p0+v0-round,p1-v1,p0+v0-cPoint,p1-v1,p0+v0-round,p1-v1,7, true); 108 | } else { 109 | this.v.setTripleAt(p0-v0,p1+v1,p0-v0+cPoint,p1+v1,p0-v0,p1+v1,2); 110 | this.v.setTripleAt(p0-v0,p1-v1,p0-v0,p1-v1+cPoint,p0-v0,p1-v1,3); 111 | } 112 | }else{ 113 | this.v.setTripleAt(p0+v0,p1-v1+round,p0+v0,p1-v1+cPoint,p0+v0,p1-v1+round,0, true); 114 | if(round!== 0){ 115 | this.v.setTripleAt(p0+v0-round,p1-v1,p0+v0-round,p1-v1,p0+v0-cPoint,p1-v1,1, true); 116 | this.v.setTripleAt(p0-v0+round,p1-v1,p0-v0+cPoint,p1-v1,p0-v0+round,p1-v1,2, true); 117 | this.v.setTripleAt(p0-v0,p1-v1+round,p0-v0,p1-v1+round,p0-v0,p1-v1+cPoint,3, true); 118 | this.v.setTripleAt(p0-v0,p1+v1-round,p0-v0,p1+v1-cPoint,p0-v0,p1+v1-round,4, true); 119 | this.v.setTripleAt(p0-v0+round,p1+v1,p0-v0+round,p1+v1,p0-v0+cPoint,p1+v1,5, true); 120 | this.v.setTripleAt(p0+v0-round,p1+v1,p0+v0-cPoint,p1+v1,p0+v0-round,p1+v1,6, true); 121 | this.v.setTripleAt(p0+v0,p1+v1-round,p0+v0,p1+v1-round,p0+v0,p1+v1-cPoint,7, true); 122 | } else { 123 | this.v.setTripleAt(p0-v0,p1-v1,p0-v0+cPoint,p1-v1,p0-v0,p1-v1,1, true); 124 | this.v.setTripleAt(p0-v0,p1+v1,p0-v0,p1+v1-cPoint,p0-v0,p1+v1,2, true); 125 | this.v.setTripleAt(p0+v0,p1+v1,p0+v0-cPoint,p1+v1,p0+v0,p1+v1,3, true); 126 | 127 | } 128 | }*/ 129 | 130 | 131 | 132 | var p0 = rectangleData.p.k[0], p1 = rectangleData.p.k[1], v0 = rectangleData.s.k[0]/2, v1 = rectangleData.s.k[1]/2; 133 | var round = Math.min(v0,v1,rectangleData.r.k); 134 | var cPoint = round*(1-roundCorner); 135 | var vPoints = []; 136 | var oPoints = []; 137 | var iPoints = []; 138 | if(rectangleData.d === 2 || rectangleData.d === 1) { 139 | vPoints[0] = [p0+v0, p1-v1+round]; 140 | oPoints[0] = [p0+v0 - vPoints[0][0], p1-v1+round - vPoints[0][1]]; 141 | iPoints[0] = [p0+v0 - vPoints[0][0], p1-v1+cPoint - vPoints[0][1]]; 142 | vPoints[1] = [p0+v0, p1+v1-round]; 143 | oPoints[1] = [p0+v0 - vPoints[1][0], p1+v1-cPoint - vPoints[1][1]]; 144 | iPoints[1] = [p0+v0 - vPoints[1][0], p1+v1-round - vPoints[1][1]]; 145 | 146 | if(round!== 0){ 147 | vPoints[2] = [p0+v0-round, p1+v1]; 148 | oPoints[2] = [p0+v0-round - vPoints[2][0], p1+v1 - vPoints[2][1]]; 149 | iPoints[2] = [p0+v0-cPoint - vPoints[2][0], p1+v1 - vPoints[2][1]]; 150 | vPoints[3] = [p0-v0+round, p1+v1]; 151 | oPoints[3] = [p0-v0+cPoint - vPoints[3][0], p1+v1 - vPoints[3][1]]; 152 | iPoints[3] = [p0-v0+round - vPoints[3][0], p1+v1 - vPoints[3][1]]; 153 | vPoints[4] = [p0-v0, p1+v1-round]; 154 | oPoints[4] = [p0-v0 - vPoints[4][0], p1+v1-round - vPoints[4][1]]; 155 | iPoints[4] = [p0-v0 - vPoints[4][0], p1+v1-cPoint - vPoints[4][1]]; 156 | vPoints[5] = [p0-v0, p1-v1+round]; 157 | oPoints[5] = [p0-v0 - vPoints[5][0], p1-v1+cPoint - vPoints[5][1]]; 158 | iPoints[5] = [p0-v0 - vPoints[5][0], p1-v1+round - vPoints[5][1]]; 159 | vPoints[6] = [p0-v0+round, p1-v1]; 160 | oPoints[6] = [p0-v0+round - vPoints[6][0], p1-v1 - vPoints[6][1]]; 161 | iPoints[6] = [p0-v0+cPoint - vPoints[6][0], p1-v1 - vPoints[6][1]]; 162 | vPoints[7] = [p0+v0-round, p1-v1]; 163 | oPoints[7] = [p0+v0-cPoint - vPoints[7][0], p1-v1 - vPoints[7][1]]; 164 | iPoints[7] = [p0+v0-round - vPoints[7][0], p1-v1 - vPoints[7][1]]; 165 | } else { 166 | vPoints[2] = [p0-v0, p1+v1]; 167 | oPoints[2] = [p0-v0+cPoint - vPoints[2][0], p1+v1 - vPoints[2][1]]; 168 | iPoints[2] = [p0-v0 - vPoints[2][0], p1+v1 - vPoints[2][1]]; 169 | vPoints[3] = [p0-v0, p1-v1]; 170 | oPoints[3] = [p0-v0 - vPoints[3][0], p1-v1+cPoint - vPoints[3][1]]; 171 | iPoints[3] = [p0-v0 - vPoints[3][0], p1-v1 - vPoints[3][1]]; 172 | } 173 | } else { 174 | vPoints[0] = [p0+v0, p1-v1+round]; 175 | oPoints[0] = [p0+v0 - vPoints[0][0], p1-v1+cPoint - vPoints[0][1]]; 176 | iPoints[0] = [p0+v0 - vPoints[0][0], p1-v1+round - vPoints[0][1]]; 177 | 178 | if(round!== 0){ 179 | vPoints[1] = [p0+v0-round, p1-v1]; 180 | oPoints[1] = [p0+v0-round - vPoints[1][0], p1-v1 - vPoints[1][1]]; 181 | iPoints[1] = [p0+v0-cPoint - vPoints[1][0], p1-v1 - vPoints[1][1]]; 182 | vPoints[2] = [p0-v0+round, p1-v1]; 183 | oPoints[2] = [p0-v0+cPoint - vPoints[2][0], p1-v1 - vPoints[2][1]]; 184 | iPoints[2] = [p0-v0+round - vPoints[2][0], p1-v1 - vPoints[2][1]]; 185 | vPoints[3] = [p0-v0, p1-v1+round]; 186 | oPoints[3] = [p0-v0 - vPoints[3][0], p1-v1+round - vPoints[3][1]]; 187 | iPoints[3] = [p0-v0 - vPoints[3][0], p1-v1+cPoint - vPoints[3][1]]; 188 | vPoints[4] = [p0-v0, p1+v1-round]; 189 | oPoints[4] = [p0-v0 - vPoints[4][0], p1+v1-cPoint - vPoints[4][1]]; 190 | iPoints[4] = [p0-v0 - vPoints[4][0], p1+v1-round - vPoints[4][1]]; 191 | vPoints[5] = [p0-v0+round, p1+v1]; 192 | oPoints[5] = [p0-v0+round - vPoints[5][0], p1+v1 - vPoints[5][1]]; 193 | iPoints[5] = [p0-v0+cPoint - vPoints[5][0], p1+v1 - vPoints[5][1]]; 194 | vPoints[6] = [p0+v0-round, p1+v1]; 195 | oPoints[6] = [p0+v0-cPoint - vPoints[6][0], p1+v1 - vPoints[6][1]]; 196 | iPoints[6] = [p0+v0-round - vPoints[6][0], p1+v1 - vPoints[6][1]]; 197 | vPoints[7] = [p0+v0, p1+v1-round]; 198 | oPoints[7] = [p0+v0 - vPoints[7][0], p1+v1-round - vPoints[7][1]]; 199 | iPoints[7] = [p0+v0 - vPoints[7][0], p1+v1-cPoint - vPoints[7][1]]; 200 | } else { 201 | vPoints[1] = [p0-v0, p1-v1]; 202 | oPoints[1] = [p0-v0+cPoint - vPoints[1][0], p1-v1 - vPoints[1][1]]; 203 | iPoints[1] = [p0-v0 - vPoints[1][0], p1-v1 - vPoints[1][1]]; 204 | vPoints[2] = [p0-v0, p1+v1]; 205 | oPoints[2] = [p0-v0 - vPoints[2][0], p1+v1-cPoint - vPoints[2][1]]; 206 | iPoints[2] = [p0-v0 - vPoints[2][0], p1+v1 - vPoints[2][1]]; 207 | vPoints[3] = [p0+v0, p1+v1]; 208 | oPoints[3] = [p0+v0-cPoint - vPoints[3][0], p1+v1 - vPoints[3][1]]; 209 | iPoints[3] = [p0+v0 - vPoints[3][0], p1+v1 - vPoints[3][1]]; 210 | 211 | } 212 | } 213 | var pathObject = { 214 | ks: { 215 | a:0, 216 | k: { 217 | i:iPoints, 218 | v:vPoints, 219 | o:oPoints, 220 | c: true 221 | } 222 | } 223 | } 224 | return pathObject; 225 | } 226 | 227 | function drawable(_drawableData, _level, _timeOffset) { 228 | var paths = []; 229 | var level = _level; 230 | var drawableData = _drawableData; 231 | var closed = false; 232 | var timeOffset = _timeOffset; 233 | 234 | function getDrawingAttributes(pathName) { 235 | var attributes = []; 236 | var hexColor; 237 | var color = drawableData.c; 238 | var animatedProp; 239 | if(drawableData.ty === 'st') { 240 | if (color.a === 0) { 241 | hexColor = rgbHex(color.k[0]*255,color.k[1]*255,color.k[2]*255); 242 | attributes.push({ 243 | key: 'android:strokeColor', 244 | value: hexColor 245 | }) 246 | } else { 247 | hexColor = rgbHex(color.k[0].s[0]*255,color.k[0].s[1]*255,color.k[0].s[2]*255) 248 | attributes.push({ 249 | key: 'android:strokeColor', 250 | value: hexColor 251 | }) 252 | animatedProp = property.createAnimatedProperty(pathName, 'strokeColor', color.k, timeOffset); 253 | targets.addTarget(animatedProp); 254 | } 255 | attributes.push({ 256 | key: 'android:strokeLineCap', 257 | value: 'round' 258 | }) 259 | attributes.push({ 260 | key: 'android:strokeLineJoin', 261 | value: 'round' 262 | }) 263 | 264 | if(drawableData.w.a === 0) { 265 | attributes.push({ 266 | key: 'android:strokeWidth', 267 | value: drawableData.w.k 268 | }) 269 | } else { 270 | attributes.push({ 271 | key: 'android:strokeWidth', 272 | value: drawableData.w.k[0].s 273 | }) 274 | animatedProp = property.createAnimatedProperty(pathName, 'strokeWidth', drawableData.w.k, timeOffset); 275 | targets.addTarget(animatedProp); 276 | } 277 | if(drawableData.o.a === 0) { 278 | attributes.push({ 279 | key: 'android:strokeAlpha', 280 | value: drawableData.o.k * 0.01 281 | }) 282 | } else { 283 | attributes.push({ 284 | key: 'android:strokeAlpha', 285 | value: drawableData.o.k[0].s * 0.01 286 | }) 287 | animatedProp = property.createAnimatedProperty(pathName, 'strokeAlpha', drawableData.o.k, timeOffset); 288 | targets.addTarget(animatedProp); 289 | } 290 | 291 | } else if(drawableData.ty === 'fl') { 292 | if (color.a === 0) { 293 | hexColor = rgbHex(color.k[0]*255,color.k[1]*255,color.k[2]*255) 294 | attributes.push({ 295 | key: 'android:fillColor', 296 | value: hexColor 297 | }) 298 | } else { 299 | hexColor = rgbHex(color.k[0].s[0]*255,color.k[0].s[1]*255,color.k[0].s[2]*255) 300 | attributes.push({ 301 | key: 'android:fillColor', 302 | value: hexColor 303 | }) 304 | animatedProp = property.createAnimatedProperty(pathName, 'fillColor', color.k, timeOffset); 305 | targets.addTarget(animatedProp); 306 | } 307 | if(drawableData.o.a === 0) { 308 | attributes.push({ 309 | key: 'android:fillAlpha', 310 | value: drawableData.o.k * 0.01 311 | }) 312 | } else { 313 | attributes.push({ 314 | key: 'android:fillAlpha', 315 | value: drawableData.o.k[0].s * 0.01 316 | }) 317 | animatedProp = property.createAnimatedProperty(pathName, 'fillAlpha', drawableData.o.k, timeOffset); 318 | targets.addTarget(animatedProp); 319 | } 320 | attributes.push({ 321 | key: 'android:fillType', 322 | value: drawableData.r === 1 ? 'nonZero' : 'evenOdd' 323 | }) 324 | } 325 | return attributes; 326 | } 327 | 328 | function isTransformAnimated(transform) { 329 | if(transform.p && transform.p.a === 1) { 330 | return true; 331 | } 332 | if(transform.a && transform.a.a === 1) { 333 | return true; 334 | } 335 | if(transform.s && transform.s.a === 1) { 336 | return true; 337 | } 338 | if(transform.r && transform.r.a === 1) { 339 | return true; 340 | } 341 | return false; 342 | } 343 | 344 | function addPath(path, transforms, level, trimPath) { 345 | if (closed) { 346 | return; 347 | } 348 | paths.push({path: path, transforms: transforms, level: level, trimPath: trimPath}); 349 | } 350 | 351 | function addEllipse(shapeData, transforms, level, trimPath) { 352 | if (closed) { 353 | return; 354 | } 355 | var pathConverted = convertEllipseToPath(shapeData); 356 | if(pathConverted) { 357 | paths.push({path: pathConverted, transforms: transforms, level: level, trimPath: trimPath}); 358 | } 359 | } 360 | 361 | function addRectangle(shapeData, transforms, level, trimPath) { 362 | if (closed) { 363 | return; 364 | } 365 | var pathConverted = convertRectangleToPath(shapeData); 366 | if(pathConverted) { 367 | paths.push({path: pathConverted, transforms: transforms, level: level, trimPath: trimPath}); 368 | } 369 | } 370 | 371 | function canFlattenPath(transforms, level) { 372 | var i = 0; 373 | while (i < level) { 374 | if(isTransformAnimated(transforms[i])){ 375 | return false; 376 | } 377 | i += 1; 378 | } 379 | return true; 380 | } 381 | 382 | function buildNewPath(pathList, pathName) { 383 | var pathAttributes = [].concat(getDrawingAttributes(pathName)); 384 | var pathNode = node.createNodeWithAttributes('path', pathAttributes, pathName); 385 | var finalNode = pathNode; 386 | var groupNode, nestedGroupNode, nestedArray; 387 | var i, len = pathList.length; 388 | var j, jLen; 389 | matrix.reset(); 390 | var transforms; 391 | var finalPathData = ''; 392 | var animatedProp, currentAnimatedProp; 393 | var currentPath, pathData; 394 | for(i = 0; i < len; i += 1){ 395 | pathData = pathList[i]; 396 | transforms = pathData.transforms; 397 | jLen = pathData.level; 398 | matrix.reset(); 399 | 400 | if(!canFlattenPath(transforms, jLen)){ 401 | for(j = jLen - 1; j >= 0; j -= 1) { 402 | nestedArray = [finalNode].concat(createTransformGroup(pathName + naming.GROUP_NAME +'_' + j, transforms[j], timeOffset)); 403 | finalNode = node.nestArray(nestedArray); 404 | var name = node.getAttribute(finalNode, 'android:name'); 405 | 406 | //parentGroupNode = node.createNode('group', pathName + '_gr_' + j); 407 | //groupNode = createTransformGroup(parentGroupNode, transforms[j], timeOffset); 408 | //node.nestChild(parentGroupNode, finalNode); 409 | //finalNode = groupNode; 410 | } 411 | } else { 412 | for(j = 0; j < jLen; j += 1) { 413 | matrix.translate(transforms[j].p.k[0], transforms[j].p.k[1]); 414 | matrix.scale(transforms[j].s.k[0]/100, transforms[j].s.k[1]/100); 415 | matrix.rotate(transforms[j].r.k*degToRads); 416 | matrix.translate(-transforms[j].a.k[0], -transforms[j].a.k[1]); 417 | } 418 | } 419 | 420 | if(pathData.path.ks.a === 0) { 421 | currentPath = ' ' + createPathData(pathData.path.ks.k, matrix); 422 | finalPathData += currentPath; 423 | if(animatedProp) { 424 | var aaptAttr = node.getChild(animatedProp,'aapt:attr'); 425 | var setProp = node.getChild(aaptAttr,'set'); 426 | var setChildren = node.getChildren(setProp); 427 | jLen = setChildren.length; 428 | var objectAnimator, value; 429 | for(j = 0; j < jLen; j += 1) { 430 | value = node.getAttribute(setChildren[j],'android:valueFrom'); 431 | if(value) { 432 | node.addAttribute(setChildren[j],'android:valueFrom', value + currentPath); 433 | value = node.getAttribute(setChildren[j],'android:valueTo'); 434 | node.addAttribute(setChildren[j],'android:valueTo', value + currentPath); 435 | } 436 | } 437 | } 438 | } else { 439 | if(animatedProp) { 440 | if(pathData.path.ks.k[0].t > 0) { 441 | var extraKeyframe = JSON.parse(JSON.stringify(pathData.path.ks.k[0])); 442 | extraKeyframe.e = extraKeyframe.s; 443 | extraKeyframe.t = 0; 444 | pathData.path.ks.k.splice(0,0,extraKeyframe); 445 | } 446 | var aaptAttr = node.getChild(animatedProp,'aapt:attr'); 447 | var setProp = node.getChild(aaptAttr,'set'); 448 | var setChildren = node.getChildren(setProp); 449 | jLen = setChildren.length; 450 | var objectAnimator, value; 451 | for(j = 0; j < jLen; j += 1) { 452 | value = node.getAttribute(setChildren[j],'android:valueFrom'); 453 | if(value) { 454 | node.addAttribute(setChildren[j],'android:valueFrom', value + createPathData(pathData.path.ks.k[j - 1].s[0], matrix)); 455 | value = node.getAttribute(setChildren[j],'android:valueTo'); 456 | var endValue = 'e' in pathData.path.ks.k[j - 1] ? pathData.path.ks.k[j - 1].e[0] : pathData.path.ks.k[j].s[0] 457 | node.addAttribute(setChildren[j],'android:valueTo', value + createPathData(endValue, matrix)); 458 | } 459 | } 460 | } else { 461 | animatedProp = property.createAnimatedPathData(pathName, pathData.path.ks.k, matrix, finalPathData, timeOffset); 462 | currentPath = ' ' + createPathData(pathData.path.ks.k[0].s[0], matrix); 463 | finalPathData += currentPath; 464 | targets.addTarget(animatedProp); 465 | } 466 | } 467 | 468 | if(pathData.trimPath) { 469 | var trimPathData = pathData.trimPath; 470 | var startValue, endValue, offsetValue; 471 | if (trimPathData.s.a === 0) { 472 | startValue = trimPathData.s.k * 0.01; 473 | } else { 474 | startValue = trimPathData.s.k[0].s * 0.01; 475 | animatedProp = property.createAnimatedProperty(pathName, 'trimPathStart', trimPathData.s.k, timeOffset); 476 | targets.addTarget(animatedProp); 477 | } 478 | if (trimPathData.e.a === 0) { 479 | endValue = trimPathData.e.k * 0.01; 480 | } else { 481 | endValue = trimPathData.e.k[0].s * 0.01; 482 | animatedProp = property.createAnimatedProperty(pathName, 'trimPathEnd', trimPathData.e.k, timeOffset); 483 | targets.addTarget(animatedProp); 484 | } 485 | if (trimPathData.o.a === 0) { 486 | offsetValue = trimPathData.o.k * 1/360; 487 | } else { 488 | offsetValue = trimPathData.o.k[0].s * 1/360; 489 | animatedProp = property.createAnimatedProperty(pathName, 'trimPathOffset', trimPathData.o.k, timeOffset); 490 | targets.addTarget(animatedProp); 491 | } 492 | node.addAttribute(pathNode,'android:trimPathStart', startValue); 493 | node.addAttribute(pathNode,'android:trimPathEnd', endValue); 494 | node.addAttribute(pathNode,'android:trimPathOffset', offsetValue); 495 | } 496 | } 497 | node.addAttribute(pathNode,'android:pathData', finalPathData); 498 | 499 | return finalNode; 500 | } 501 | 502 | function keyframesAreEqual(keyframes1, keyframes2) { 503 | if(keyframes1.length !== keyframes2.length) { 504 | return false; 505 | } 506 | var i = 0, len = keyframes1.length; 507 | while (i