├── .gitignore
├── src
├── Commands
│ ├── Optimizing
│ │ ├── OptimizeKeyframes.json
│ │ ├── Clear Extra Blank Keyframes.jsfl
│ │ ├── Usage Report.jsfl
│ │ ├── Filter Finder.jsfl
│ │ ├── Optimize Keyframes.jsfl
│ │ ├── Optimize Graphics.jsfl
│ │ └── HTML5 Canvas Size Report.jsfl
│ ├── Timeline
│ │ ├── AnimationEventLabel.json
│ │ └── Animation Event Label.jsfl
│ ├── Exporting
│ │ ├── Export Frame Label Sequences.jsfl
│ │ └── Export BitmapMovieClip.jsfl
│ └── Utilities
│ │ └── Copy Layers To Bitmap.jsfl
├── JSFLLibraries
│ ├── ArrayUtils.jsfl
│ ├── ObjectUtils.jsfl
│ ├── JSON.jsfl
│ ├── Command.jsfl
│ └── XULWindow.jsfl
├── io.springroll.toolkit.mxi
└── Tools
│ └── HTMLCanvasPostPublish.jsfl
├── AlphaSplitter.zip
├── .travis.yml
├── package.json
├── LICENSE
├── Gruntfile.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | settings.json
2 | node_modules
3 | FlashToolkit.zxp
4 |
--------------------------------------------------------------------------------
/src/Commands/Optimizing/OptimizeKeyframes.json:
--------------------------------------------------------------------------------
1 | {"moveTolerance":1,"scaleTolerance":0.01}
--------------------------------------------------------------------------------
/AlphaSplitter.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SpringRoll/FlashToolkit/HEAD/AlphaSplitter.zip
--------------------------------------------------------------------------------
/src/Commands/Timeline/AnimationEventLabel.json:
--------------------------------------------------------------------------------
1 | {
2 | "value" : false,
3 | "_isBasic" : true
4 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | before_install:
5 | - npm install -g grunt-cli
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.3.2",
3 | "private": true,
4 | "devDependencies": {
5 | "grunt": "^0.4.5",
6 | "grunt-contrib-clean": "^0.6.0",
7 | "grunt-contrib-jshint": "^0.11.2",
8 | "grunt-exec": "^0.4.6",
9 | "grunt-simple-version": "^0.2.4"
10 | },
11 | "scripts": {
12 | "test": "grunt test --verbose"
13 | }
14 | }
--------------------------------------------------------------------------------
/src/JSFLLibraries/ArrayUtils.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * Add utility methods to the Array class
5 | * @class Array.prototype
6 | */
7 |
8 | /**
9 | * See if an array contains a value
10 | * @method contains
11 | * @param {mixed} needle The value to check for
12 | */
13 | Array.prototype.contains = function(needle)
14 | {
15 | for(var k in this)
16 | {
17 | if (this[k] == needle) { return true; }
18 | }
19 | return false;
20 | };
21 |
22 | }());
--------------------------------------------------------------------------------
/src/JSFLLibraries/ObjectUtils.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * Add utility methods to the Object class
5 | * @class Object.prototype
6 | */
7 |
8 | /**
9 | * See if an array contains a value
10 | * @method contains
11 | * @param {mixed} needle The value to check for
12 | */
13 | Object.prototype.contains = function(needle)
14 | {
15 | for(var k in this)
16 | {
17 | if (this[k] == needle) { return true; }
18 | }
19 | return false;
20 | };
21 |
22 | }());
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 SpringRoll
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 |
23 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt)
2 | {
3 | grunt.loadNpmTasks('grunt-contrib-jshint');
4 | grunt.loadNpmTasks('grunt-contrib-clean');
5 | grunt.loadNpmTasks('grunt-simple-version');
6 | grunt.loadNpmTasks('grunt-exec');
7 |
8 | var settings;
9 |
10 | if (grunt.file.exists('settings.json'))
11 | {
12 | settings = grunt.file.readJSON('settings.json');
13 | }
14 | else
15 | {
16 | grunt.log.error("To build the FlashToolkit there must be a settings.json " +
17 | "file which contains keys 'packager' (path to ZXPSignCmd), 'keystore' " +
18 | "(path to p12 certificate) and 'storepass' (certificate password).");
19 | }
20 |
21 | grunt.initConfig({
22 | settings: settings || {},
23 | output: 'FlashToolkit.zxp',
24 | jshint: {
25 | all: {
26 | src: [
27 | 'Gruntfile.js',
28 | 'src/‘**/*.jsfl'
29 | ]
30 | }
31 | },
32 | exec: {
33 | package: "\"<%= settings.packager %>\" " +
34 | "-sign " +
35 | "src " +
36 | "\"<%= output %>\" " +
37 | "\"<%= settings.keystore %>\" " +
38 | "<%= settings.storepass %>"
39 |
40 | },
41 | version: {
42 | options: {
43 | "src/io.springroll.toolkit.mxi": function(content, version)
44 | {
45 | return content.replace(
46 | /version\=\".+?\"\>/,
47 | 'version="' + version + '">'
48 | );
49 | }
50 | }
51 | },
52 | clean: {
53 | build: ["<%= output %>"]
54 | }
55 | });
56 |
57 | grunt.registerTask(
58 | 'default',
59 | 'Default build and package',
60 | [
61 | 'version:current',
62 | 'clean:build',
63 | 'test',
64 | 'exec:package'
65 | ]
66 | );
67 |
68 | grunt.registerTask(
69 | 'test',
70 | 'Test to run for continuous integration',
71 | ['jshint:all']
72 | );
73 | };
--------------------------------------------------------------------------------
/src/JSFLLibraries/JSON.jsfl:
--------------------------------------------------------------------------------
1 | (function(global){
2 |
3 | /**
4 | * The JSON serialization and unserialization methods
5 | * @class JSON
6 | */
7 | var JSON = {};
8 |
9 | JSON.prettyPrint = false;
10 |
11 | /**
12 | * implement JSON.stringify serialization
13 | * @method stringify
14 | * @param {Object} obj The object to convert
15 | */
16 | JSON.stringify = function(obj)
17 | {
18 | return _internalStringify(obj, 0);
19 | };
20 |
21 | function _internalStringify(obj, depth, fromArray)
22 | {
23 | var t = typeof (obj);
24 | if (t != "object" || obj === null)
25 | {
26 | // simple data type
27 | if (t == "string") return '"'+obj+'"';
28 | return String(obj);
29 | }
30 | else
31 | {
32 | // recurse array or object
33 | var n, v, json = [], arr = (obj && obj.constructor == Array);
34 |
35 | var joinString, bracketString, firstPropString;
36 | if(JSON.prettyPrint)
37 | {
38 | joinString = ",\n";
39 | bracketString = "\n";
40 | for(var i = 0; i < depth; ++i)
41 | {
42 | joinString += "\t";
43 | bracketString += "\t";
44 | }
45 | joinString += "\t";//one extra for the properties of this object
46 | firstPropString = bracketString + "\t";
47 | }
48 | else
49 | {
50 | joinString = ",";
51 | firstPropString = bracketString = "";
52 | }
53 | for (n in obj)
54 | {
55 | v = obj[n]; t = typeof(v);
56 |
57 | // Ignore functions
58 | if (t == "function") continue;
59 |
60 | if (t == "string") v = '"'+v+'"';
61 | else if (t == "object" && v !== null) v = _internalStringify(v, depth + 1, arr);
62 |
63 | json.push((arr ? "" : '"' + n + '":') + String(v));
64 | }
65 | return (fromArray || depth === 0 ? "" : bracketString)+ (arr ? "[" : "{") + firstPropString + json.join(joinString) + bracketString + (arr ? "]" : "}");
66 | }
67 | }
68 |
69 | /**
70 | * Implement JSON.parse de-serialization
71 | * @method parse
72 | * @param {String} str The string to de-serialize
73 | */
74 | JSON.parse = function(str)
75 | {
76 | if (str === "") str = '""';
77 | eval("var p=" + str + ";"); // jshint ignore:line
78 | return p;
79 | };
80 |
81 | // Assign to global space
82 | global.JSON = JSON;
83 |
84 | }(window));
--------------------------------------------------------------------------------
/src/io.springroll.toolkit.mxi:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/Commands/Exporting/Export Frame Label Sequences.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | // Include the command library
4 | var dir = fl.scriptURI.split("/");
5 | dir = dir.slice(0,dir.length - 3).join("/");
6 | fl.runScript(dir + "/JSFLLibraries/Command.jsfl");
7 |
8 | /**
9 | * Export a Bitmap sequence for all frame labels on the
10 | * current timeline.
11 | * @class FrameSequence
12 | */
13 | var FrameSequence = function()
14 | {
15 | // Check for document
16 | if (!this.getDOM()) return;
17 |
18 | /**
19 | * The current timeline
20 | * @property {Timeline} timeline
21 | */
22 | this.timeline = null;
23 |
24 | /**
25 | * The animation labels
26 | * @property {array} animationLabels
27 | */
28 | this.animationLabels = [];
29 |
30 | var folderURI = fl.browseForFolderURL("Select a folder to export to");
31 |
32 | if(!folderURI) return;
33 |
34 | folderURI += "/";
35 |
36 | fl.outputPanel.clear();
37 |
38 | this.timeline = this.dom.getTimeline();
39 |
40 | var layers = this.timeline.layers,
41 | j = 0,
42 | k = 0,
43 | layer,
44 | frame;
45 |
46 | // Remove any stop or loop frames
47 | for (j=0; j < layers.length; j++)
48 | {
49 | layer = layers[j];
50 | if (layer.layerType != "guided" && layer.layerType != "folder")
51 | {
52 | for (k=0; k < layer.frames.length;)
53 | {
54 | frame = layer.frames[k];
55 | if (frame.name.search(/ stop$| loop$/) > -1)
56 | {
57 | this.timeline.clearKeyframes(frame.startFrame);
58 | }
59 | k += frame.duration;//increment k to get to the next frame
60 | }
61 | }
62 | }
63 |
64 | // Get all of the frame labels
65 | for (j=0; j < layers.length; j++)
66 | {
67 | layer = layers[j];
68 | if (layer.layerType != "guided" && layer.layerType != "folder")
69 | {
70 | for (k=0; k < layer.frames.length;)
71 | {
72 | frame = layer.frames[k];
73 | if (frame.name && frame.name !== "")
74 | {
75 | this.animationLabels.push(frame);
76 | }
77 | //increment k to get to the next frame
78 | k += frame.duration;
79 | }
80 | }
81 | }
82 |
83 | if (this.animationLabels.length === 0)
84 | {
85 | alert("no animations found");
86 | return;
87 | }
88 |
89 | for (j=0; j < this.animationLabels.length; ++j)
90 | {
91 | var f = this.animationLabels[j];
92 |
93 | // because exportInstanceToPNGSequence() doesn't
94 | // work the way we want it to, we get to
95 | // do this the old fashioned way.
96 | this.exportPNGSequence(folderURI + f.name, f.startFrame + 1, f.startFrame + f.duration);
97 | }
98 | };
99 |
100 | var p = FrameSequence.prototype = new Command();
101 |
102 | /**
103 | * Export a PNG sequence
104 | * @method exportPNGSequence
105 | * @param {string} fileURI_noExt The full URI without an extension
106 | * @param {int} startFrame The starting frame number
107 | * @param {int} endFrame The ending frame number
108 | */
109 | p.exportPNGSequence = function(fileURI_noExt, startFrame, endFrame)
110 | {
111 | var frameCount = 0;
112 | for (var i = startFrame - 1; i < endFrame; ++i)
113 | {
114 | frameCount++;
115 | this.timeline.currentFrame = i;
116 | this.dom.exportPNG(fileURI_noExt + padNum(frameCount.toString()) + ".png", true, true);
117 | }
118 | };
119 |
120 | /**
121 | * Add padding to a number
122 | * @param {int} input The input number
123 | * @return {string} The padded number
124 | */
125 | var padNum = function(input)
126 | {
127 | while (input.length < 4)
128 | input = "0" + input;
129 | return input;
130 | };
131 |
132 | new FrameSequence();
133 |
134 | }());
--------------------------------------------------------------------------------
/src/JSFLLibraries/Command.jsfl:
--------------------------------------------------------------------------------
1 | /**
2 | * @module window
3 | */
4 | (function(global){
5 |
6 | /**
7 | * An abstract command for extending cloudkid's commands
8 | * @class Command
9 | */
10 | var Command = function()
11 | {
12 | /**
13 | * The current document
14 | * @property {Document} dom
15 | */
16 | this.dom = null;
17 |
18 | /**
19 | * If the DOM is an html5 canvas FLA
20 | * @property {Boolean} isCanvas
21 | */
22 | this.isCanvas = false;
23 | };
24 |
25 | // reference to the prototype
26 | var p = Command.prototype = {};
27 |
28 | /**
29 | * Get the parent directory of a path or file
30 | * @method dirname
31 | * @param {String} path The path to get the parent directory for
32 | * @return {String} The parent path
33 | */
34 | Command.dirname = function(path)
35 | {
36 | // Remove the last slash if it's there
37 | if (path.substr(-1, 1) == "/")
38 | {
39 | path = path.substr(0, path.length - 1);
40 | }
41 |
42 | // Get the parent directory
43 | var dir = path.split("/");
44 | dir.pop();
45 | dir = dir.join("/") + "/";
46 |
47 | return dir;
48 | };
49 |
50 | /**
51 | * Get the config directory, relative to this directory
52 | * this is a better version than configURI, for debugging
53 | * purposese.
54 | * @method dir
55 | * @static
56 | * @readOnly
57 | */
58 | Command.dir = function()
59 | {
60 | return Command.dirname(
61 | Command.dirname(fl.scriptURI)
62 | );
63 | }();
64 |
65 | /**
66 | * Require a library file or additional script
67 | * @method require
68 | * @static
69 | * @param {Object} path The relative path to file
70 | */
71 | Command.require = function(path)
72 | {
73 | fl.runScript(Command.dir + path);
74 | };
75 |
76 | // Include required utilities classes
77 | Command.require("JSFLLibraries/JSON.jsfl");
78 | Command.require("JSFLLibraries/ArrayUtils.jsfl");
79 | Command.require("JSFLLibraries/ObjectUtils.jsfl");
80 |
81 | /**
82 | * Get the settings from the settings file
83 | * @method loadSettings
84 | * @param {String} path The relative path to file
85 | * @return {String} The settings to grab
86 | */
87 | p.loadSettings = function(path)
88 | {
89 | var value = FLfile.read(Command.dir + path);
90 | if (value)
91 | {
92 | value = JSON.parse(value);
93 | if (value._isBasic)
94 | {
95 | value = value.value;
96 | }
97 | }
98 | return value;
99 | };
100 |
101 | /**
102 | * Get the settings
103 | * @method loadSettings
104 | * @param {String} path The relative path to file
105 | * @param {mixed} [value=""] The value to save
106 | * @return {Boolean} If the save was successful
107 | */
108 | p.saveSettings = function(path, value)
109 | {
110 | value = value || "";
111 |
112 | if (typeof value != "object")
113 | {
114 | value = {
115 | "value" : value,
116 | "_isBasic" : true
117 | };
118 | }
119 | if (!FLfile.write(Command.dir + path, JSON.stringify(value)))
120 | {
121 | alert("Unable to save to " + path);
122 | return false;
123 | }
124 | return true;
125 | };
126 |
127 | /**
128 | * Create a new dialog
129 | * @method dialog
130 | * @param {String} title The title of the dialog
131 | * @param {*} [button] The collection of buttons
132 | */
133 | p.dialog = function(title)
134 | {
135 | //if (!global.XULWindow)
136 | //{
137 | Command.require("JSFLLibraries/XULWindow.jsfl");
138 | //}
139 | return new global.XULWindow(title, "accept", "cancel");
140 | };
141 |
142 | /**
143 | * Get the current document and check if it's available,
144 | * set's document
145 | * @method getDOM
146 | * @return {Document} Returns null if no document, also does alert
147 | */
148 | p.getDOM = function()
149 | {
150 | var dom = fl.getDocumentDOM();
151 |
152 | if (!dom)
153 | {
154 | alert("No document opened");
155 | return;
156 | }
157 | this.dom = dom;
158 | this.isCanvas = dom.type == "htmlcanvas";
159 |
160 | return dom;
161 | };
162 |
163 | // Assign to the namespace
164 | global.Command = Command;
165 |
166 | }(window));
--------------------------------------------------------------------------------
/src/Commands/Timeline/Animation Event Label.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | // Include the command library
4 | var dir = fl.scriptURI.split("/");
5 | dir = dir.slice(0,dir.length - 3).join("/");
6 | fl.runScript(dir + "/JSFLLibraries/Command.jsfl");
7 |
8 | /**
9 | * This script allows adding frame labels form animation events
10 | * used by the Animation library.
11 | * @class AnimationEventLabel
12 | * @extends Command
13 | */
14 | var AnimationEventLabel = function()
15 | {
16 | if (!this.getDOM()) return;
17 |
18 | var timeline = this.dom.getTimeline();
19 | var sel = timeline.getSelectedFrames();
20 |
21 | if (sel === "")
22 | {
23 | alert("No frames selected!");
24 | return;
25 | }
26 |
27 | // The collection of labels
28 | var labels = this.getLabels(timeline);
29 |
30 | // The settings file
31 | this.settingsFile = 'Commands/Utilities/AnimationEventLabel.json';
32 |
33 | // The sections selected
34 | var sections = sel.length / 3;
35 |
36 | // Create the dialog so we can reuse it
37 | var dialog = this.createDialog();
38 |
39 | for (var i=0; i < sections; i++)
40 | {
41 | var l = sel[i*3];
42 | var sf = sel[i*3 + 1];
43 | var ef = sel[i*3 + 2] - 1;
44 |
45 | // Check that there is a selection
46 | if ( ef - sf <= 0 )
47 | {
48 | alert("Selecting a single frame is not a valid selection.");
49 | break;
50 | }
51 |
52 | // Get the settings
53 | var settings = dialog.create();
54 |
55 | // Silently dismiss
56 | if (settings.dismiss != "accept") return;
57 |
58 | var label = settings.label;
59 | var underscore = this.isCanvas ? true : settings.underscore == "true";
60 |
61 | // If we're not canvas save the underscore setting
62 | // so we can reset next time
63 | if (!this.isCanvas)
64 | {
65 | this.saveSettings(this.settingsFile, underscore);
66 | }
67 |
68 | var action = (underscore ? "_" : " ") + settings.action;
69 |
70 | // No label was provided
71 | if (!label)
72 | {
73 | alert("Label cannot be empty.");
74 | break;
75 | }
76 |
77 | // Already contains the labels!
78 | if (labels.contains(label) || labels.contains(label + action))
79 | {
80 | alert("Label '"+label+"' is already taken.");
81 | break;
82 | }
83 |
84 | timeline.currentLayer = l;
85 |
86 | var start = timeline.layers[l].frames[sf];
87 | var end = timeline.layers[l].frames[ef];
88 |
89 | // If the start frame is not a keyframe
90 | if (!start || start.startFrame != sf)
91 | {
92 | timeline.convertToBlankKeyframes(sf);
93 | start = timeline.layers[l].frames[sf];
94 | }
95 |
96 | // If the end frame is not a keyframe
97 | if (!end || end.startFrame != ef )
98 | {
99 | timeline.convertToBlankKeyframes(ef);
100 | end = timeline.layers[l].frames[ef];
101 | }
102 |
103 | // Add the start label
104 | start.labelType = "name";
105 | start.name = label;
106 |
107 | // Add the stop label
108 | end.labelType = "name";
109 | end.name = label + action;
110 | }
111 | };
112 |
113 | // Extends Command
114 | var p = AnimationEventLabel.prototype = new Command();
115 |
116 | /**
117 | * Get a collection of labels on the current timeline
118 | * @method getLabels
119 | * @param {Timeline} timeline The current timeline
120 | */
121 | p.getLabels = function(timeline)
122 | {
123 | var labels = [];
124 |
125 | // Get all of the frame labels
126 | for (var j=0; j < timeline.layers.length; j++)
127 | {
128 | var layer = timeline.layers[j];
129 | if (layer.layerType != "guided" && layer.layerType != "folder")
130 | {
131 | for (var k=0; k < layer.frames.length;)
132 | {
133 | var frame = layer.frames[k];
134 |
135 | if (frame.name !== "")
136 | {
137 | labels.push(frame.name);
138 | }
139 | k += frame.duration;
140 | }
141 | }
142 | }
143 | return labels;
144 | };
145 |
146 | /**
147 | * Create a dialog to create the frame labels
148 | * @method createDialog
149 | * @return {XULWindow} The created dialog
150 | */
151 | p.createDialog = function()
152 | {
153 | // Ask for the name of the label
154 | var dialog = this.dialog("Animation Event Label");
155 |
156 | var actions = [
157 | {label:"Stop",value:"stop"},
158 | {label:"Loop",value:"loop"},
159 | ];
160 |
161 | dialog.columns(2, 2)
162 | .openRow()
163 | .addLabel("Event Name", "label", 100)
164 | .addTextBox("label", "", 150)
165 | .closeRow()
166 | .openRow()
167 | .addLabel("Action Type", "action", 100)
168 | .addRadioGroup("action", actions)
169 | .closeRow();
170 |
171 | // Only give the javascript option
172 | // if we are not the html canvas type fla
173 | if (!this.isCanvas)
174 | {
175 | var underscore = this.loadSettings(this.settingsFile);
176 | dialog.addCheckbox("Javascript Compliant", "underscore", underscore);
177 | }
178 | return dialog;
179 | };
180 |
181 | // Run
182 | new AnimationEventLabel();
183 |
184 | }());
--------------------------------------------------------------------------------
/src/Commands/Optimizing/Clear Extra Blank Keyframes.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * Command to remove extraneous blank keyframes
5 | * @class ClearExtraBlankKeyframes
6 | */
7 | var ClearExtraBlankKeyframes = function()
8 | {
9 | /**
10 | * The cache of items that have already been searched
11 | * @property {Array} objectsSearched
12 | */
13 | this.objectsSearched = [];
14 |
15 | /**
16 | * The total number of empty keyframes removed
17 | * @property {int} totalRemoved
18 | */
19 | this.totalRemoved = 0;
20 |
21 | this.initialize();
22 | };
23 |
24 | // Reference to the prototype
25 | var p = ClearExtraBlankKeyframes.prototype = {};
26 |
27 | /**
28 | * Initialize
29 | * @method initialize
30 | */
31 | p.initialize = function()
32 | {
33 | var doc = fl.getDocumentDOM();
34 |
35 | if (!doc)
36 | {
37 | alert("No document opened");
38 | return;
39 | }
40 |
41 | var curTimelines = doc.timelines;
42 | var i;
43 |
44 | // Search through the timelines of this document
45 | for(i = 0; i < curTimelines.length; ++i)
46 | {
47 | this.clearByTimeline(curTimelines[i]);
48 | }
49 |
50 | var libraryItems = doc.library.items;
51 |
52 | // Search through library items not put on stage
53 | for(i = 0; i < libraryItems.length; ++i)
54 | {
55 | this.searchLibraryItem(libraryItems[i]);
56 | }
57 |
58 | if (this.totalRemoved > 0)
59 | {
60 | alert("Cleared " + this.totalRemoved + " extra blank keyframe" + (this.totalRemoved > 1 ? "s" : ""));
61 | }
62 | else
63 | {
64 | alert("No extra blank keyframes cleared");
65 | }
66 | };
67 |
68 | /**
69 | * Clear extra empty keyframes by a timeline
70 | * @method clearByTimeline
71 | * @param {Timeline} timeline The timeline to clear frames from
72 | */
73 | p.clearByTimeline = function(timeline)
74 | {
75 | var j = 0,
76 | k = 0,
77 | l = 0,
78 | el,
79 | num,
80 | prevNum = -1,
81 | frm,
82 | layer,
83 | lyrVisibility;
84 |
85 | // cycle through each of the layers in this timeline
86 | for(j = 0; j < timeline.layers.length; ++j)
87 | {
88 | // cycle through each of the layers in this timeline
89 | layer = timeline.layers[j];
90 |
91 | // Ignore non-normal layers
92 | if (layer.layerType != "normal")
93 | {
94 | continue;
95 | }
96 | timeline.setSelectedLayers(j);
97 |
98 | // store the layer visibility and then make the layer visible.
99 | // Elements cannot be found on invisible layers
100 | lyrVisibility = layer.visible;
101 | layer.visible = true;
102 |
103 | // Loop through the frames
104 | for(k = 0; k < layer.frames.length; )
105 | {
106 | // step through the frames on this layer
107 | frm = layer.frames[k];
108 | num = frm.elements.length;
109 |
110 | // Match empty keyframes
111 | if (num === 0)
112 | {
113 | // Previous keyframe has no elements and
114 | // there's no sound on this frame
115 | if (prevNum === 0 && !frm.soundLibraryItem && !frm.name)
116 | {
117 | timeline.clearKeyframes(k);
118 | this.totalRemoved++;
119 | }
120 | }
121 | else
122 | {
123 | for (l = 0; l < num; ++l)
124 | {
125 | // then cycle through the elements on this frame
126 | el = frm.elements[l];
127 |
128 | if (el.elementType == "instance")
129 | {
130 | // lets get the library item for this element
131 | this.searchLibraryItem(el.libraryItem);
132 | }
133 | }
134 | }
135 | prevNum = num;
136 | k += frm.duration;
137 | }
138 | prevNum = -1;
139 |
140 | // return this layer to its original visibility (optional)
141 | layer.visible = lyrVisibility;
142 | }
143 | };
144 |
145 | /**
146 | * Search the library by item
147 | * @method searchLibraryItem
148 | * @param {LibraryItem} libItem The library item to search for
149 | */
150 | p.searchLibraryItem = function(libItem)
151 | {
152 | var validSymbolTypes = ["graphic", "movie clip", "button"];
153 |
154 | // Ignore invalid symbol types
155 | if (!inObject(libItem.itemType, validSymbolTypes))
156 | {
157 | this.objectsSearched.push(libItem.name);
158 | return;
159 | }
160 |
161 | // only process new objects and not runtime shared-assets
162 | if ( !inObject(libItem.name, this.objectsSearched))
163 | {
164 | this.objectsSearched.push(libItem.name);
165 |
166 | if (libItem.timeline)
167 | {
168 | // if there is a timeline, repeat the scan as a recursion
169 | this.clearByTimeline(libItem.timeline);
170 | }
171 | }
172 | };
173 |
174 | /**
175 | * utility function to check if a value is in an object or array
176 | * @method inObject
177 | * @private
178 | * @static
179 | * @param {*} needle The value to check
180 | * @param {Object|Array} haystack The object to check in
181 | * @param {Boolean} If the needle is in the haystack
182 | */
183 | var inObject = function(needle, haystack)
184 | {
185 | for(var k in haystack )
186 | {
187 | if ( haystack[k] == needle ) { return true; }
188 | }
189 | return false;
190 | };
191 |
192 | // Run the command
193 | new ClearExtraBlankKeyframes();
194 |
195 | }());
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SpringRoll Flash Toolkit [](https://travis-ci.org/SpringRoll/FlashToolkit) [](https://david-dm.org/SpringRoll/FlashToolkit#info=devDependencies) [](http://badge.fury.io/gh/SpringRoll%2FFlashToolkit)
2 |
3 | Adobe Flash Professional Commands for Exporting, Optimizing and Publish HTML5 Canvas document. While these commands are exclusive to using SpringRoll, there are a couple helper function for use with certain SpringRoll features.
4 |
5 | ## Installation
6 |
7 | * Visit the latest release [here](https://github.com/SpringRoll/FlashToolkit/releases)
8 | * Download **FlashToolkit.zxp**
9 | * CC & CC 2014: Install ZXP with the [Adobe Extension Manager CC](https://www.adobe.com/exchange/em_download/).
10 | * CC 2015: Install ZXP with the [Manage Extensions app](https://blogs.adobe.com/flashpro/installing-zxp-extensions-using-the-manage-extensions-utility/)
11 |
12 | ## Usage
13 |
14 | All the scripts are runable from the Commands menu within Flash Professional.
15 |
16 | #### Exporting/Export BitmapMovieClip
17 |
18 | Create JSON and spritesheet useable for `springroll.easeljs.BitmapMovieClip`. To use, select a MovieClip or MovieClips from the library to export then run the command.
19 |
20 | #### Exporting/Export Frame Label Sequence
21 |
22 | Export a sequence of PNGs based on the frame labels. Useful if you're creating spritesheets using TexturePacker. To use, run the command on the current timeline which contains frame labels.
23 |
24 | #### Optimizing/Clear Extra Blank Keyframe
25 |
26 | Remove all redundant clear keyframes. In earlier versions of CC and CC 2014, blank keyframes added more filesize and produced playback issues in CreateJS. To use, run the command on the current timeline.
27 |
28 | #### Optimizing/Filter Finder
29 |
30 | Generate a report of all the MovieClips within the current project which contain filters (Color transforms, Drop shadows, etc). This is useful for tracking down and remove filters which harm performance. To use, simply run the command.
31 |
32 | #### Optimizing/HTML5 Canvas Size Report
33 |
34 | Generate a report of the output size of all the assets in an HTML5 Canvas published document. To use, publish the current document and then run the command.
35 |
36 | #### Optimizing/Optimize Graphics
37 |
38 | Remove all unused graphic frame from a selected graphic symbol in the library. To run, select a graphic from the library and then run the command.
39 |
40 | #### Optimizing/Optimize Keyframe
41 |
42 | Reduce the number of keyframes by eliminating keyframes that are similar. To run, navigate to a timeline of frames you would like to remove, then run the command. There are two values "Move Tolerance" and "Scale Tolerance". If two keyframes contain a symbol within those tolerance the second keyframe is removed.
43 |
44 | #### Timeline/Animation Event Label
45 |
46 | Create animation event labels on the timeline which work with `springroll.easeljs.Animator`. To use, select a range of frames on the current timeline and run the command. This will prompt for an event name to create a start and end label for use with Animator. For instance, "idle" event name will create "idle" and "idle_stop" labels. Animator has two types of event labels, `stop` (stop at the end) and `loop` (return to the beginning and keep playing).
47 |
48 | #### Utilities/Copy Layers To Bitmap
49 |
50 | This command combines all layers on the current timeline non-destructively into a single bitmap. All the layers are guided, locked and hidden. Running the script again will undo this convert and give you the normal vector layers.
51 |
52 | #### HTML5 Canvas Post Publish
53 |
54 | This is not a command. This script automatically is invoked whenever an HTML5 Canvas FLA is published. It does two things: first, a JSON file is created of the `lib.properties` (which contains the manifest, size, framerate, etc) of the current document. Also, the output file is optimize to better work with a JavaScript minification tool (e.g., [Uglify](https://github.com/mishoo/UglifyJS2)).
55 |
56 | ## Building Toolkit
57 |
58 | ### Dependencies
59 |
60 | To rebuild the extension for Flash, you need to have [Node](http://nodejs.org) and [Grunt](http://gruntjs.com/getting-started) installed.
61 |
62 | Download and install the [Adobe Exchange Packager](https://www.adobeexchange.com/resources) (you will need to login to the Adobe Exchange to be able to download the application).
63 |
64 | ### Create Environment Settings
65 |
66 | Create a **settings.json** file within the FlashToolkit project folder with the following format:
67 |
68 | ```json
69 | {
70 | "packager": "/Applications/Adobe Exchange Packager.app/Contents/Resources/macosx/ZXPSignCmd",
71 | "keystore": "/Users/username/Documents/MySignedCertificate.p12",
72 | "storepass": "myCertPassword"
73 | }
74 | ```
75 | You may need to make the ZXPSignCmd file executable, as it might not have the needed permissions by default.
76 |
77 | ### Building the ZXP
78 |
79 | Rebuild the Toolkit by running the default Grunt task:
80 |
81 | ```bash
82 | grunt
83 | ```
84 |
85 | ## License
86 |
87 | Copyright (c) 2015 [CloudKid](https://github.com/cloudkidstudio)
88 |
89 | Released under the MIT License.
90 |
--------------------------------------------------------------------------------
/src/Tools/HTMLCanvasPostPublish.jsfl:
--------------------------------------------------------------------------------
1 | (function(window)
2 | {
3 | fl.addEventListener("postPublish", onPostPublish);
4 |
5 | function onPostPublish()
6 | {
7 | fl.showIdleMessage(false);
8 | postPublish();
9 | fl.showIdleMessage(true);
10 | }
11 |
12 | function postPublish()
13 | {
14 | // Get the current document
15 | var dom = fl.getDocumentDOM();
16 |
17 | // Ignore unsaved FLAs
18 | if (!dom.pathURI) return;
19 |
20 | // Check for canvas
21 | if (dom.type != "htmlcanvas") return;
22 |
23 | // The folder URI
24 | var folder = dirname(dom.pathURI);
25 |
26 | // If the FLA is uncompressed use the parent
27 | // folder for relative publish pathing
28 | if (/\.xfl$/i.test(dom.pathURI))
29 | {
30 | folder = dirname(folder);
31 | }
32 |
33 | // Add the trailing slash
34 | folder += "/";
35 |
36 | // Get the file name
37 | var filename = exportFile(dom);
38 |
39 | // No filename found
40 | if (!filename) return;
41 |
42 | var file = folder + filename;
43 |
44 | // Output file doesn't exist
45 | if (!FLfile.exists(file)) return;
46 |
47 | var content = FLfile.read(file);
48 |
49 | //find the 'var lib, images, createj, ss;' line at the end of the file
50 | var varFinder = /var (\w+), (\w+), (\w+), (\w+);$/m;
51 | //also, the Flash 2014 'var lib, images, createj;'
52 | var oldVarFinder = /var (\w+), (\w+), (\w+);$/m;
53 |
54 | if(!(varFinder.exec(content) || oldVarFinder.exec(content)))
55 | {
56 | alert("File was published improperly. Please quit Flash and try publishing again.");
57 | return;
58 | }
59 |
60 | // Extract the properties
61 | var regex = new RegExp("properties = [^;]+;", "gm");
62 | var result = regex.exec(content);
63 | if (result)
64 | {
65 | var properties;
66 | eval(String(result)); // jshint ignore:line
67 | FLfile.write(file + "on", stringify(properties));
68 | content = content.replace(/[\w]+\.properties = [^;]+;/gm, '');
69 | }
70 |
71 | content = classRef(content, "cjs.Shape");
72 | content = classRef(content, "cjs.Container");
73 | content = classRef(content, "cjs.Rectangle");
74 | content = classRef(content, "cjs.Tween");
75 | content = classRef(content, "cjs.Bitmap");
76 | content = classRef(content, "cjs.MovieClip");
77 | content = classRef(content, "cjs.Sprite");
78 | content = classRef(content, "cjs.Ease");
79 |
80 | var libItem = /\(([\w\d]+)\.[\w\d]+? \= function\(.*?\) \{([\s\S]+?)\}\)\.prototype \= p/gm;
81 | var libItems = content.match(libItem);
82 |
83 | if (libItems)
84 | {
85 | libItems.forEach(function(lib)
86 | {
87 | //var name = lib.substring(1, lib.indexOf(" ="));
88 | //fl.trace("Symbol " + name);
89 | var start = lib.indexOf('{') + 1;
90 | var end = lib.lastIndexOf('}');
91 | var libContent = lib.substring(start, end);
92 | var matches = libContent.match(/this\.([\w]+?) =/g);
93 |
94 | if (matches)
95 | {
96 | // add the var block at the beginning
97 | for(var id, i = 0; i < matches.length; i++)
98 | {
99 | id = matches[i].substr(5).replace(" =", '');
100 | libContent = "\n\tvar " + id + ";" + libContent;
101 | libContent = libContent.replace(
102 | new RegExp("this\." + id + "([\.\,\)\}\;])", "g"),
103 | id + "$1"
104 | );
105 | }
106 |
107 | // Replace the "this.instance =" with "instance = this.instance ="
108 | libContent = libContent.replace(/this\.([\w]+?) =/g, "$1 = this.$1 =");
109 | }
110 |
111 | // Update the content
112 | content = content.replace(lib, lib.substr(0, start) + libContent + lib.substr(end));
113 | });
114 | }
115 | FLfile.write(file, content);
116 | }
117 |
118 | /**
119 | * Get the director path from another path
120 | * @method dirname
121 | * @private
122 | * @param {string} path The incoming path
123 | * @return {string} The base parent folder
124 | */
125 | function dirname(path)
126 | {
127 | return path.replace(/\\/g, '/')
128 | .replace(/\/[^\/]*\/?$/, '');
129 | }
130 |
131 | /**
132 | * Get an export filename
133 | * @method exportFile
134 | * @return {String|null} The export filename
135 | */
136 | function exportFile(dom)
137 | {
138 | // We need to get some information from the publish profile
139 | // this includes the path to the images, the libs namespace
140 | // and the export file name
141 | var profile = dom.exportPublishProfileString(),
142 | xml = new XML(profile),
143 | properties = xml.PublishProperties,
144 | name;
145 |
146 | for (var i = 0, len = properties.length(); i < len; i++)
147 | {
148 | name = properties[i].attribute('name');
149 |
150 | // Ignore other PublishProperties properties other than HTML
151 | if (name != "JavaScript/HTML") continue;
152 |
153 | // Get the list of properties
154 | var props = properties[i].Property;
155 |
156 | // Find the export filename
157 | for (var j = 0, l = props.length(); j < l; j++)
158 | {
159 | name = props[j].attribute('name');
160 | if (name == "filename") return String(props[j]);
161 | }
162 | }
163 | return null;
164 | }
165 |
166 | /**
167 | * Substitute a class reference
168 | * @method classRef
169 | * @param {String} content The javascript content
170 | * @param {String} className The filename to replace
171 | * @return {String} The updated content
172 | */
173 | function classRef(content, className)
174 | {
175 | var regex = new RegExp(className.replace(".", "\\."), "g");
176 | if (content.match(regex))
177 | {
178 | var token = "var p; // shortcut to reference prototypes";
179 | var name = className.substr(className.indexOf('.') + 1);
180 | //fl.trace("Class " + className + " replace with " + name);
181 | content = content.replace(regex, name);
182 | content = content.replace(token, token += "\nvar " + name + " = " + className + ";");
183 | }
184 | return content;
185 | }
186 |
187 | function stringify(obj)
188 | {
189 | var t = typeof (obj);
190 | if (t != "object" || obj === null)
191 | {
192 | // simple data type
193 | if (t == "string") obj = '"' + obj + '"';
194 | return String(obj);
195 | }
196 | else
197 | {
198 | // recurse array or object
199 | var n, v, json = [], arr = (obj && obj.constructor == Array);
200 |
201 | for (n in obj)
202 | {
203 | v = obj[n];
204 | t = typeof(v);
205 | if (obj.hasOwnProperty(n))
206 | {
207 | if (t == "string")
208 | v = '"' + v + '"';
209 | else if (t == "object" && v !== null)
210 | v = stringify(v);
211 | json.push((arr ? "" : '"' + n + '":') + String(v));
212 | }
213 | }
214 | return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
215 | }
216 | }
217 |
218 | }(window));
--------------------------------------------------------------------------------
/src/Commands/Optimizing/Usage Report.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | // Include the command library
4 | var dir = fl.scriptURI.split("/");
5 | dir = dir.slice(0,dir.length - 3).join("/");
6 | fl.runScript(dir + "/JSFLLibraries/Command.jsfl");
7 |
8 | /*
9 | * This script parses all of the items inside of a Flash document (fla)
10 | * and give a report of all the references.
11 | * @class UsageReport
12 | * @extends Command
13 | */
14 | var UsageReport = function()
15 | {
16 | if (!this.getDOM()) return;
17 |
18 | /**
19 | * The objects we've checked already
20 | * @property {Array} objectsUsed
21 | */
22 | this.objectsUsed = [];
23 |
24 | /**
25 | * The output report
26 | * @property {Array} output
27 | */
28 | this.output = [];
29 |
30 | /**
31 | * The valid type of symbols
32 | * @property {Array} validTypes
33 | */
34 | this.validTypes = ["movie clip","graphic","button"];
35 |
36 | /**
37 | * The item we're searching for
38 | * @property {Item} item
39 | */
40 | this.item = null;
41 |
42 | var curTimelines = this.dom.timelines;
43 | var libraryItems = this.dom.library.items;
44 |
45 | // Clear the output window
46 | fl.outputPanel.clear();
47 |
48 | var items = this.dom.library.getSelectedItems();
49 |
50 | if (items.length != 1)
51 | {
52 | alert("Select a library item to start.");
53 | return;
54 | }
55 |
56 | // Get the library item
57 | this.item = items[0];
58 |
59 | // Search through the timelines of this document
60 | for(var i = 0; i < curTimelines.length; ++i)
61 | {
62 | this.searchInTimeline(curTimelines[i], "(Main Timeline)");
63 | }
64 |
65 | // Search through library items not put on stage
66 | for(i = 0; i < libraryItems.length; ++i)
67 | {
68 | this.searchInItem(libraryItems[i]);
69 | }
70 |
71 | fl.trace("\nSearching for: " + this.item.name);
72 | fl.trace("Usages found: " + this.output.length + "\n\n");
73 |
74 | this.tableLayout(["Timeline", "Layer", "Frame"], this.output);
75 | };
76 |
77 | // Extend the command
78 | var p = UsageReport.prototype = new Command();
79 |
80 | /**
81 | * The number of space to pad the report with
82 | * @property {int} PADDING
83 | * @final
84 | * @readOnly
85 | */
86 | UsageReport.PADDING = 4;
87 |
88 | /**
89 | * Create a table layout
90 | * @method tableLayout
91 | * @param {Array} header The names of the headers
92 | * @param {Array} entries The list of entries
93 | */
94 | p.tableLayout = function(headers, entries)
95 | {
96 | var finalStr = "";
97 | var layout = [];
98 |
99 | // Set the minimum values
100 | for (i = 0; i < headers.length; ++i)
101 | {
102 | layout[i] = headers[i].length + UsageReport.PADDING;
103 | }
104 |
105 | // Measure the layout for prettier display
106 | for (i = 0; i < entries.length; ++i)
107 | {
108 | for(j = 0; j < layout.length; ++j)
109 | {
110 | layout[j] = Math.max(layout[j], String(entries[i][j]).length + UsageReport.PADDING);
111 | }
112 | }
113 |
114 | // Show the table headers
115 | var res = "";
116 | var total = 0;
117 | for (j = 0; j < layout.length; ++j)
118 | {
119 | res += this.fontLayout(headers[j], layout[j]);
120 | total += layout[j];
121 | }
122 | finalStr += res + "\n";
123 |
124 | // Place a divider
125 | var hr = "";
126 | for (i = 0; i < total; ++i)
127 | {
128 | hr += "-";
129 | }
130 |
131 | finalStr += hr + "\n";
132 |
133 | // Prep the entries
134 | for (i = 0; i < entries.length; ++i)
135 | {
136 | res = "";
137 | for (j = 0; j < layout.length; ++j)
138 | {
139 | res += this.fontLayout(String(entries[i][j]), layout[j]);
140 | }
141 | finalStr += res + "\n";
142 | }
143 |
144 | finalStr += hr + "\n";
145 |
146 | fl.trace(finalStr);
147 | };
148 |
149 | /**
150 | * For the table layout, add padding to a string
151 | * @method fontLayout
152 | * @param {String} str The string to layout
153 | * @param {int} len The number of spaces to pad
154 | * @return {String} The padded string
155 | */
156 | p.fontLayout = function(str, len)
157 | {
158 | while(str.length < len)
159 | {
160 | str = str + " ";
161 | }
162 | return str;
163 | };
164 |
165 | /**
166 | * Search for a library item within an item
167 | * @method searchInItem
168 | * @param {Item} libItem The item to search within
169 | */
170 | p.searchInItem = function(libItem)
171 | {
172 | // Ignore shared runtime libraries
173 | if (!this.isCanvas && libItem.linkageImportForRS) return;
174 |
175 | // only process new objects and not runtime shared-assets
176 | if (this.validTypes.contains(libItem.itemType) && !this.objectsUsed.contains(libItem.name))
177 | {
178 | this.objectsUsed.push(libItem.name);
179 |
180 | if (libItem.timeline)
181 | {
182 | // if there is a timeline, repeat the scan as a recursion
183 | this.searchInTimeline(libItem.timeline, libItem.name);
184 | }
185 | }
186 | };
187 |
188 | /**
189 | * Find the usage within a timeline
190 | * @method searchInTimeline
191 | * @param {Timeline} tLine THe time to look withint
192 | * @param {String} timelineName The name of the timeline
193 | */
194 | p.searchInTimeline = function(tLine, timelineName)
195 | {
196 | var j = 0;
197 | var k = 0;
198 | var l = 0;
199 | var el;
200 | var frm;
201 | var layer;
202 | var lyrVisibility;
203 |
204 | // cycle through each of the layers in this timeline
205 | for(j = 0; j < tLine.layers.length; ++j )
206 | {
207 | // cycle through each of the layers in this timeline
208 | layer = tLine.layers[j];
209 |
210 | // store the layer visibility and then make the layer visible.
211 | // Elements cannot be found on invisible layers
212 | lyrVisibility = layer.visible;
213 | layer.visible = true;
214 |
215 | for(k = 0; k < layer.frames.length;)
216 | {
217 | // step through the frames on this layer
218 | frm = layer.frames[k];
219 |
220 | for (l = 0; l < frm.elements.length; ++l )
221 | {
222 | // then cycle through the elements on this frame
223 | el = frm.elements[l];
224 |
225 | // If the element is a text field, check the font face
226 | if (el.elementType == "instance")
227 | {
228 | if (el.libraryItem.name == this.item.name)
229 | {
230 | var entry = [];
231 |
232 | entry.push(timelineName);
233 | entry.push(layer.name);
234 | entry.push(k+1);
235 |
236 | this.output.push(entry);
237 | }
238 | else
239 | {
240 | this.searchInItem(el.libraryItem);
241 | }
242 | }
243 | }
244 | k += frm.duration;
245 | }
246 | // return this layer to its original visibility (optional)
247 | layer.visible = lyrVisibility;
248 | }
249 | };
250 |
251 | new UsageReport();
252 |
253 | }());
--------------------------------------------------------------------------------
/src/Commands/Optimizing/Filter Finder.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * This script parses all of the items inside of a Flash document (fla)
5 | * and alerts the user to replace the font face with something else.
6 | * @class FilterFinder
7 | */
8 | var FilterFinder = function()
9 | {
10 | var doc = fl.getDocumentDOM();
11 |
12 | // Check for document
13 | if (!doc) return;
14 |
15 | /**
16 | * Collection of the library item names used to keep track of what we've already searched
17 | * @property {Array} objectsUsed
18 | * @private
19 | */
20 | this.objectsUsed = [];
21 |
22 | /**
23 | * The output result
24 | * @property {Array} output
25 | * @private
26 | */
27 | this.output = [];
28 |
29 | var i;
30 | var curTimelines = doc.timelines;
31 | var libraryItems = doc.library.items;
32 |
33 | this.isCanvas = doc.type == "htmlcanvas";
34 |
35 | // Create the headers
36 | var headers = [
37 | 'symbol',
38 | 'timeline',
39 | 'layer',
40 | 'frame',
41 | 'filters',
42 | 'colorized'
43 | ];
44 |
45 | // Clear the output window
46 | fl.outputPanel.clear();
47 |
48 | // Search through the timelines of this document
49 | for (i = 0; i < curTimelines.length; ++i)
50 | {
51 | this.findFilters(curTimelines[i], "(Main Timeline)");
52 | }
53 |
54 | // Search through library items not put on stage
55 | for (i = 0; i < libraryItems.length; ++i)
56 | {
57 | this.searchLibraryItem(libraryItems[i]);
58 | }
59 |
60 | // Check for no finds
61 | if (this.output.length < 1)
62 | {
63 | alert("Document contains no filters or colorizations.");
64 | return;
65 | }
66 |
67 | fl.trace("\nINSTANCE FILTERS");
68 | fl.trace(this.tableLayout(headers, this.output));
69 | };
70 |
71 | // Save the prototype
72 | var p = FilterFinder.prototype;
73 |
74 | /**
75 | * The amount of padding in the output report
76 | * @property {int} PADDING
77 | * @static
78 | * @readOnly
79 | * @default 4
80 | */
81 | var PADDING = 4;
82 |
83 | /**
84 | * Generate an ASCII table
85 | * @method tableLayout
86 | * @private
87 | * @param {Array} header Collection of header labels
88 | * @param {Array} entries Collection of array entries
89 | * @return {String} The full ASCII table
90 | */
91 | p.tableLayout = function(headers, entries)
92 | {
93 | var finalStr = "";
94 |
95 | // How wide each column is
96 | var layout = [];
97 |
98 | // Set the minimum values
99 | for (i = 0; i < headers.length; ++i)
100 | {
101 | layout[i] = headers[i].length + PADDING;
102 | }
103 |
104 | // Measure the layout for prettier display
105 | for (i = 0; i < entries.length; ++i)
106 | {
107 | for (j = 0; j < layout.length; ++j)
108 | {
109 | layout[j] = Math.max(layout[j], String(entries[i][headers[j]]).length + PADDING);
110 | }
111 | }
112 |
113 | // Show the table headers
114 | res = "";
115 | var total = 0;
116 | for (j = 0; j < layout.length; ++j)
117 | {
118 | var th = headers[j];
119 | res += fontLayout(th[0].toUpperCase() + th.substr(1), layout[j]);
120 | total += layout[j];
121 | }
122 | finalStr += res + "\n";
123 |
124 | // Place a divider
125 | var hr = "";
126 | for (i = 0; i < total; ++i)
127 | {
128 | hr += "-";
129 | }
130 |
131 | finalStr += hr + "\n";
132 |
133 | // Prep the entries
134 | for (i = 0; i < entries.length; ++i)
135 | {
136 | res = "";
137 | for (j = 0; j < layout.length; ++j)
138 | {
139 | res += fontLayout(String(entries[i][headers[j]]), layout[j]);
140 | }
141 | finalStr += res + "\n";
142 | }
143 |
144 | finalStr += hr + "\n";
145 | return "\n" + finalStr;
146 | };
147 |
148 | /**
149 | * Prepend string with text based on a fixed length
150 | * @method fontLayout
151 | * @param {String} str THe string to pad
152 | * @param {int} len The length of the output string
153 | */
154 | var fontLayout = function(str, len)
155 | {
156 | while(str.length < len)
157 | {
158 | str = str + " ";
159 | }
160 | return str;
161 | };
162 |
163 | /**
164 | * Find a text fields on a timeline
165 | * @method findFilters
166 | * @param {Timeline} tLine The flash Timeline object
167 | * @param {String} timelineName The name of the timeline for reporting
168 | */
169 | p.findFilters = function(tLine, timelineName)
170 | {
171 | var j = 0;
172 | var k = 0;
173 | var l = 0;
174 | var el;
175 | var frm;
176 | var layer;
177 | var lyrVisibility;
178 |
179 | // cycle through each of the layers in this timeline
180 | for (j = 0; j < tLine.layers.length; ++j )
181 | {
182 | // cycle through each of the layers in this timeline
183 | layer = tLine.layers[j];
184 |
185 | // store the layer visibility and then make the layer visible.
186 | // Elements cannot be found on invisible layers
187 | lyrVisibility = layer.visible;
188 | layer.visible = true;
189 |
190 | //fl.trace("layer is : '"+layer.name+"'");
191 | for (k = 0; k < layer.frames.length;)
192 | {
193 | // step through the frames on this layer
194 | frm = layer.frames[k];
195 |
196 | for ( l = 0; l < frm.elements.length; ++l )
197 | {
198 | // then cycle through the elements on this frame
199 | el = frm.elements[l];
200 |
201 | // If the element is a text field, check the font face
202 | if (el.elementType == "instance" && el.instanceType == "symbol")
203 | {
204 | var colorized = el.colorMode != "none" && el.colorMode != "alpha";
205 | var filters = el.filters ? el.filters.length : 0;
206 | if (filters > 0 || colorized)
207 | {
208 | this.output.push({
209 | symbol: el.libraryItem.name,
210 | timeline: timelineName,
211 | layer: layer.name,
212 | frame: k + 1,
213 | filters: filters,
214 | colorized: colorized
215 | });
216 | }
217 |
218 | // lets get the library item for this element
219 | this.searchLibraryItem(el.libraryItem);
220 | }
221 | }
222 | // Go to the next keyframe
223 | k += frm.duration;
224 | }
225 | // return this layer to its original visibility (optional)
226 | layer.visible = lyrVisibility;
227 | }
228 | };
229 |
230 | /**
231 | * Search for fonts within a library item
232 | * @searchLibraryItem
233 | * @param {Item} libItem The library item
234 | */
235 | p.searchLibraryItem = function(libItem)
236 | {
237 | var validTypes = ["movie clip","graphic","button"];
238 | var runtimeShared = !this.isCanvas && libItem.linkageImportForRS;
239 |
240 | // only process new objects and not runtime shared-assets
241 | if ( validTypes.contains(libItem.itemType) &&
242 | !this.objectsUsed.contains(libItem.name) &&
243 | !runtimeShared)
244 | {
245 | this.objectsUsed.push(libItem.name);
246 |
247 | // get the timeline of this library symbol
248 | libTLine = libItem.timeline;
249 |
250 | if (libTLine)
251 | {
252 | // if there is a timeline, repeat the scan as a recursion
253 | this.findFilters(libTLine, libItem.name);
254 | }
255 | }
256 | };
257 |
258 | /**
259 | * See if an array contains a value
260 | * @method contains
261 | * @param {mixed} needle The value to check for
262 | */
263 | Array.prototype.contains = function(needle)
264 | {
265 | for(var k in this)
266 | {
267 | if (this[k] == needle) { return true; }
268 | }
269 | return false;
270 | };
271 |
272 | new FilterFinder();
273 |
274 | }());
--------------------------------------------------------------------------------
/src/Commands/Utilities/Copy Layers To Bitmap.jsfl:
--------------------------------------------------------------------------------
1 | (function()
2 | {
3 | var dom = fl.getDocumentDOM();
4 |
5 | if (!dom) return;
6 |
7 | var timeline = dom.getTimeline();
8 | var folderLayer = timeline.layers[0];
9 |
10 | var i, bitmapLayer, bitmap, frame;
11 |
12 | if (folderLayer.name == "vectors")
13 | {
14 | // Get the bitmap layer
15 | var bitmapIndex = timeline.layers.length - 1;
16 | bitmapLayer = timeline.layers[bitmapIndex];
17 |
18 | // Delete all the bitmaps
19 | for(i = 0; i < bitmapLayer.frames.length; i++)
20 | {
21 | frame = bitmapLayer.frames[i]
22 | if (frame.elements.length)
23 | {
24 | bitmap = frame.elements[0].libraryItem;
25 | dom.library.deleteItem(bitmap.name);
26 | }
27 | }
28 |
29 | // Delete the bitmap layer
30 | timeline.deleteLayer(bitmapIndex);
31 |
32 | // Delete the vectors layer
33 | folderLayer.visible = true;
34 | folderLayer.locked = false;
35 | folderLayer.layerType = "normal";
36 | timeline.deleteLayer(0);
37 |
38 | for (i = timeline.layers.length - 1; i >= 0; i--)
39 | {
40 | timeline.layers[i].layerType = "normal";
41 | }
42 | return;
43 | }
44 |
45 | var bitmapName;
46 | var scale = localToGlobalScale();
47 |
48 | // The prompt was cancelled
49 | if (scale === null)
50 | {
51 | return;
52 | }
53 |
54 | // If we're inside a symbol, use the name of the
55 | if (timeline.libraryItem)
56 | {
57 | var item = timeline.libraryItem;
58 | bitmapName = item.name;
59 | var index = bitmapName.indexOf('/');
60 | if (index > -1)
61 | {
62 | bitmapName = bitmapName.substr(index + 1);
63 | }
64 | }
65 | else
66 | {
67 | if (!dom.name)
68 | {
69 | return alert("Please save document first.");
70 | }
71 |
72 | // Chop of the ".fla" or ".xfl" extension
73 | bitmapName = dom.name.substr(0, dom.name.lastIndexOf('.'));
74 | }
75 |
76 | // The number of layers
77 | var origLength = timeline.layers.length;
78 |
79 | // Copy the current layers
80 | timeline.copyLayers(0, origLength - 1);
81 |
82 | // Create a new folder for the hidden, guided, locked vector layers
83 | var folderLayerIndex = timeline.addNewLayer('vectors', 'folder');
84 |
85 | // Make sure the folder is first
86 | timeline.reorderLayer(folderLayerIndex, 0);
87 |
88 | // Copy to the folder
89 | timeline.pasteLayers(0);
90 |
91 | // Guide out the child layers
92 | for(i = 1; i <= origLength; i++)
93 | {
94 | timeline.layers[i].layerType = "guide";
95 | }
96 |
97 | // Lock and hide all the vectors
98 | var parentLayer = timeline.layers[0];
99 | parentLayer.visible = false;
100 | parentLayer.locked = true;
101 |
102 | // Add a new bitmap layer above the copied layers
103 | var bitmapLayerIndex = origLength + 1;
104 | timeline.setSelectedLayers(bitmapLayerIndex);
105 | timeline.addNewLayer('bitmap', 'normal', true);
106 | bitmapLayer = timeline.layers[bitmapLayerIndex];
107 |
108 | var EMPTY = -1;
109 | var KEYFRAME = 1;
110 |
111 | // Select the contents of the original layers
112 | var status;
113 | var numFrames = timeline.frameCount;
114 | for(i = numFrames - 1; i >=0 ; --i)
115 | {
116 | timeline.currentFrame = i;
117 |
118 | // Check the status of the current frame
119 | // 0 = no keyframes
120 | // 1 = keyframes no elements
121 | // 2 = keyframes + elements
122 | status = frameStatus(bitmapLayerIndex + 1, i);
123 |
124 | // Current frame has no keyframes, no content
125 | if (status < KEYFRAME)
126 | {
127 | if (status == EMPTY)
128 | {
129 | timeline.setSelectedLayers(bitmapLayerIndex);
130 | timeline.insertBlankKeyframe();
131 | }
132 | continue;
133 | }
134 |
135 | //ensure that there is a blank keyframe there to paste into
136 | selectFrame(bitmapLayerIndex, i);
137 | if (i > 0) timeline.insertBlankKeyframe(); // don't insert on the first frame
138 |
139 | // Copy all the frames and paste on the bitmap layer
140 | dom.selectAll();
141 | dom.clipCopy();
142 | selectFrame(bitmapLayerIndex, i);
143 | dom.clipPaste(true);
144 |
145 | // Scale the selection
146 | dom.transformSelection(scale, 0, 0, scale);
147 |
148 | // Convert the selection to a bitmap
149 | dom.convertSelectionToBitmap();
150 |
151 | // Undo scale to the selection
152 | var bitmap = bitmapLayer.frames[i].elements[0];
153 | dom.selection = [bitmap];
154 | dom.transformSelection(1/scale, 0, 0, 1/scale);
155 |
156 | // Get the library item from the instance and rename it
157 | if (bitmapName)
158 | {
159 | var bitmapItem = bitmap.libraryItem;
160 | bitmapItem.name = bitmapName + (i+1);
161 | }
162 | }
163 |
164 | // Delete the rest of the layers
165 | while(bitmapLayerIndex + 1 < timeline.layers.length)
166 | {
167 | timeline.deleteLayer(timeline.layers.length - 1);
168 | }
169 |
170 | function selectFrame(layer, frame)
171 | {
172 | // Select the current frame
173 | timeline.setSelectedLayers(layer);
174 | timeline.setSelectedFrames(frame, frame + 1);
175 | }
176 |
177 | // Function to check the current status of a frame
178 | // -1 = no content
179 | // 0 = content but no keyframe
180 | // 1 = keyframes + content
181 | function frameStatus(index, currentFrame)
182 | {
183 | var layer = timeline.layers[index];
184 | var status = -1; // empty
185 | var frame;
186 | while(layer)
187 | {
188 | // if (currentFrame >= layer.frameCount) continue;
189 |
190 | frame = layer.frames[currentFrame];
191 |
192 | // Has content on it
193 | if (frame && frame.elements.length)
194 | {
195 | status = 0;
196 | if (frame.startFrame == i)
197 | {
198 | status = 1;
199 | break;
200 | }
201 | }
202 | layer = timeline.layers[++index];
203 | }
204 | return status;
205 | }
206 |
207 | function localToGlobalScale()
208 | {
209 | var doc = fl.getDocumentDOM();
210 |
211 | if (!doc) return;
212 |
213 | var scaleX = 1;
214 | var scaleY = 1;
215 | var scale;
216 |
217 | var timeline = doc.getTimeline();
218 | var originalItem = libraryItem = timeline.libraryItem;
219 | var steps = 0;
220 |
221 | // We're on the main stage, ignore the rest set to 100%
222 | if (!libraryItem)
223 | {
224 | return 1;
225 | }
226 |
227 | var scaleKey = 'copyLayersToBitmapScale';
228 |
229 | while(libraryItem)
230 | {
231 | // Go "up" a nested level
232 | doc.exitEditMode();
233 | steps++;
234 |
235 | // Get the new timeline
236 | timeline = doc.getTimeline();
237 |
238 | var element = doc.selection.length ? doc.selection[0] : null;
239 | if (element && element.libraryItem == libraryItem)
240 | {
241 | scaleX *= element.scaleX;
242 | scaleY *= element.scaleY;
243 | }
244 | else
245 | {
246 | fl.outputPanel.clear();
247 | fl.trace("WARNING: Unable to measure the relative scale either because the current item was opened directly from the library ");
248 | fl.trace(" or because a tween is preventing the exit and enter of the symbol. Prompting for scale...");
249 |
250 | // Go back into the symbol after we exited
251 | doc.library.editItem(originalItem.name);
252 |
253 | // Get the saved scale amount
254 | var defaultScale = originalItem.hasData(scaleKey) ?
255 | originalItem.getData(scaleKey) : 1;
256 |
257 | // Aask for the scale
258 | var scale = prompt("Output scale", defaultScale);
259 |
260 | if (!scale) return null;
261 |
262 | scale = parseFloat(scale);
263 |
264 | // Save the scale to use at the default later on
265 | originalItem.addData(scaleKey, "double", scale);
266 |
267 | return scale;
268 | }
269 | libraryItem = timeline.libraryItem;
270 | }
271 |
272 | // Go back to where we started
273 | if (steps)
274 | {
275 | while(steps--)
276 | {
277 | if (doc.selection.length)
278 | {
279 | doc.enterEditMode("inPlace");
280 | }
281 | }
282 | }
283 |
284 | // Do a little rounding
285 | scaleX = Math.round(scaleX * 100000) / 100000;
286 | scaleY = Math.round(scaleY * 100000) / 100000;
287 |
288 | // Get the larger scale size
289 | scale = Math.max(scaleX, scaleY);
290 |
291 | // Save the scale to the library item if we have it
292 | if (originalItem)
293 | {
294 | // Save the scale to use at the default later on
295 | originalItem.addData(scaleKey, "double", scale);
296 | }
297 |
298 | // Get the largest scale
299 | return scale;
300 | }
301 |
302 | }());
303 |
--------------------------------------------------------------------------------
/src/Commands/Exporting/Export BitmapMovieClip.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | // Include the command library
4 | var dir = fl.scriptURI.split("/");
5 | dir = dir.slice(0,dir.length - 3).join("/");
6 | fl.runScript(dir + "/JSFLLibraries/Command.jsfl");
7 |
8 | /**
9 | * Export a MovieClip or Graphic as a spritesheet
10 | * with some extra JSON data that describes the timeline
11 | * @class BitmapMovieClip
12 | */
13 | var BitmapMovieClip = function()
14 | {
15 | // Check for document
16 | if (!this.getDOM()) return;
17 |
18 | JSON.prettyPrint = true;
19 |
20 | var library = this.dom.library;
21 | var selectedItems = library.getSelectedItems().slice();
22 | if(selectedItems.length === 0)
23 | {
24 | alert("You must have one or more movieclips selected in the library to export.");
25 | return;
26 | }
27 | for(var i = 0; i < selectedItems.length; ++i)
28 | {
29 | var item = selectedItems[i];
30 | if(item.itemType == "movie clip" || item.itemType == "graphic")
31 | {
32 | this.exportClip(item);
33 | }
34 | }
35 | };
36 |
37 | // Reference tot he prototype
38 | var p = BitmapMovieClip.prototype = new Command();
39 |
40 | /**
41 | * Export a single clip
42 | * @param {SymbolInstance} selectedItem The symbol movieclip or graphic to export
43 | */
44 | p.exportClip = function(selectedItem)
45 | {
46 | var symbolPath = selectedItem.name.substring(0, selectedItem.name.lastIndexOf("/") + 1),
47 | symbolName = selectedItem.name.substring(selectedItem.name.lastIndexOf("/") + 1),
48 | scaledItem,
49 | library;
50 |
51 | var scale = prompt("What scale is " + symbolName +
52 | " being exported at?\n(press cancel to skip this movieclip)", "1");
53 |
54 | if (!scale)
55 | {
56 | return;
57 | }
58 | else if(parseFloat(scale) > 0)
59 | {
60 | scale = parseFloat(scale);
61 | }
62 | else
63 | {
64 | scale = 1;
65 | }
66 |
67 | var outputObj = {};
68 |
69 | //keep track of this for proper reassembly
70 | outputObj.scale = scale;
71 | outputObj.fps = this.dom.frameRate;
72 | outputObj.labels = {};
73 |
74 | //get all those pesky frame labels
75 | var timeline = selectedItem.timeline;
76 | var layers = timeline.layers, frames;
77 |
78 | var totalLength = 0;
79 | for (var l = 0, lLen = layers.length; l < lLen; ++l)
80 | {
81 | var layer = layers[l];
82 | if(layer.layerType == "folder") continue;
83 |
84 | // hide guide/mask layers and show everything else,
85 | // for origin calculation purposes
86 | layer.visible = layer.layerType != "guide" && layer.layerType != "mask";
87 |
88 | frames = layer.frames;
89 | fLen = frames.length;
90 | if(fLen > totalLength)
91 | totalLength = fLen;
92 | for(var f = 0; f < fLen;)
93 | {
94 | var frame = frames[f];
95 | if(frame.name)
96 | {
97 | outputObj.labels[frame.name] = f;
98 | }
99 | f += frame.duration;
100 | }
101 | }
102 | // set up information on the pngs that would be exported
103 | var data = {};
104 | outputObj.frames = [data];
105 |
106 | // the name of the clip with a # to replace with the frame number
107 | data.name = symbolName + "#";
108 |
109 | // flash frame numbers start at 0 when you use the spritesheet exporter
110 | data.min = 0;
111 |
112 | // go until the end
113 | data.max = timeline.frameCount - 1;
114 |
115 | // flash frame numbers always have 4 digits
116 | data.digits = 4;
117 |
118 | //get the origin
119 | var left = 100000000;
120 | var top = 100000000;
121 |
122 | for (var i = 1, len = timeline.frameCount + 1; i < len; ++i)
123 | {
124 | var bounds = timeline.getBounds(i, false);//don't get hidden layers, aka the masks and guides we just hid
125 | if(bounds.left < left)
126 | left = bounds.left;
127 | if(bounds.top < top)
128 | top = bounds.top;
129 | }
130 | outputObj.origin = {x: -left * scale, y: -top * scale};
131 |
132 | //apply scaling if not 1
133 | if(scale != 1)
134 | {
135 | library = this.dom.library;
136 | selectedItem.name = "EBMC_TEMP";
137 | library.addNewItem("movie clip", symbolName);
138 | scaledItem = library.items[library.findItemIndex(symbolName)];
139 | library.editItem(symbolName);
140 | library.addItemToDocument({x:0, y:0}, symbolPath + "EBMC_TEMP");
141 | var element = scaledItem.timeline.layers[0].frames[0].elements[0];
142 | element.symbolType = "graphic";
143 | element.scaleX = scale;
144 | element.scaleY = scale;
145 | scaledItem.timeline.insertFrames(1, true, totalLength - 1);
146 | }
147 |
148 | //export the movieclip data
149 | var uri = fl.browseForFileURL("save", "Select a file to save the movieclip data for " + symbolName, "JSON Files (*.json)", "json");
150 | if (uri)
151 | {
152 | FLfile.write(uri, JSON.stringify(outputObj));
153 | }
154 |
155 | //For Windows, we need to supply a single file type, otherwise it gets all weird,
156 | //especially when not supplying an extension.
157 | uri = fl.browseForFileURL("save", "Select a file to save the spritesheet (png + json atlas) for " + symbolName, "Images (*.png)", "png");
158 |
159 | if (uri)
160 | {
161 | //strip off the extension, so that we can use it for both .json and .png
162 | if(uri.lastIndexOf(".") > 0)
163 | uri = uri.substring(0, uri.lastIndexOf("."));
164 |
165 | var exporter = new SpriteSheetExporter();
166 | exporter.layoutFormat = "JSON";
167 | exporter.algorithm = "maxRects";
168 | //While SpringRoll supports rotated textures, it seems like Flash's exporter doesn't
169 | //handle them properly and outputs sprites with a little bit of an offset
170 | exporter.allowRotate = false;
171 | exporter.allowTrimming = true;
172 | exporter.autoSize = true;
173 | exporter.maxSheetWidth = 2048;
174 | exporter.maxSheetHeight = 2048;
175 | exporter.shapePadding = 1;
176 | exporter.stackDuplicateFrames = true;
177 | exporter.addSymbol(scaledItem || selectedItem);
178 | if(exporter.overflowed)
179 | fl.trace("WARNING: Unable to fit all frames in 2048x2048 spritesheet");
180 | exporter.exportSpriteSheet(uri ,{
181 | format:"png",
182 | bitDepth:32,
183 | backgroundColor:"#00000000"
184 | });
185 |
186 | //read the published texture atlas and get rid of *sequential* duplicate frames, they
187 | //are easily reassembled by the TextureAtlas class used by BitmapMovieClip
188 | var exportedJSON = JSON.parse(FLfile.read(uri + ".json"));
189 | frames = exportedJSON.frames;
190 | var prevObj;
191 | for(var id in frames)
192 | {
193 | var obj = frames[id];
194 | if(typeof obj != "object")
195 | continue;
196 | if(prevObj)
197 | {
198 | if(areFramesEqual(prevObj, obj))
199 | {
200 | delete frames[id];
201 | continue;
202 | }
203 | }
204 | prevObj = obj;
205 | }
206 | JSON.prettyPrint = false;
207 | FLfile.write(uri + ".json", JSON.stringify(exportedJSON));
208 | }
209 |
210 | //undo scaling if not 1
211 | if(scale != 1)
212 | {
213 | library.deleteItem(symbolName);
214 | selectedItem.name = symbolName;
215 | }
216 | };
217 |
218 | function areFramesEqual(obj1, obj2)
219 | {
220 | /*
221 | "frame": {"x":0,"y":152,"w":118,"h":151},
222 | "rotated": false,
223 | "trimmed": false,
224 | "spriteSourceSize": {"x":0,"y":0,"w":118,"h":151},
225 | "sourceSize": {"w":118,"h":151},
226 | "pivot": {"x":0,"y":0}
227 | */
228 | if(obj1.trimmed != obj2.trimmed)
229 | return false;
230 | if(obj1.rotated != obj2.rotated)
231 | return false;
232 | var compare1 = obj1.frame, compare2 = obj2.frame;
233 | if(compare1.x != compare2.x || compare1.y != compare2.y || compare1.w != compare2.w ||
234 | compare1.h != compare2.h)
235 | {
236 | return false;
237 | }
238 | compare1 = obj1.spriteSourceSize;
239 | compare2 = obj2.spriteSourceSize;
240 | if(compare1.x != compare2.x || compare1.y != compare2.y || compare1.w != compare2.w ||
241 | compare1.h != compare2.h)
242 | {
243 | return false;
244 | }
245 | compare1 = obj1.sourceSize;
246 | compare2 = obj2.sourceSize;
247 | if(compare1.w != compare2.w || compare1.h != compare2.h)
248 | {
249 | return false;
250 | }
251 | compare1 = obj1.pivot;
252 | compare2 = obj2.pivot;
253 | if(compare1 && compare2 && (compare1.x != compare2.x || compare1.y != compare2.y))
254 | {
255 | return false;
256 | }
257 | return true;
258 | }
259 |
260 | // Run script
261 | new BitmapMovieClip();
262 |
263 | }());
--------------------------------------------------------------------------------
/src/Commands/Optimizing/Optimize Keyframes.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | // Include the command library
4 | var dir = fl.scriptURI.split("/");
5 | dir = dir.slice(0,dir.length - 3).join("/");
6 | fl.runScript(dir + "/JSFLLibraries/Command.jsfl");
7 |
8 | /**
9 | * Script to remove redundant keyframes from the timeline.
10 | * @class OptimizeKeyframes
11 | */
12 | var OptimizeKeyframes = function()
13 | {
14 | if (!this.getDOM()) return;
15 |
16 | fl.showIdleMessage(false);
17 |
18 | // Get the saved checks
19 | var settingsPath = 'Commands/Optimizing/OptimizeKeyframes.json';
20 | var settings = this.loadSettings(settingsPath) || {};
21 |
22 | /**
23 | * The tolerance when comparing a,b,c,d matrix values
24 | * @propety {Number} scaleTolerance
25 | * @default 0.1
26 | */
27 | this.scaleTolerance = settings.scaleTolerance || 0.01;
28 |
29 | /**
30 | * The tolerance when comparing translation tx, ty values
31 | * @propety {Number} moveTolerance
32 | * @default 2.0
33 | */
34 | this.moveTolerance = settings.moveTolerance || 1.0;
35 |
36 | var ui = this.dialog("Optimize Keyframes")
37 | .columns(2)
38 | .openRow()
39 | .openHBox()
40 | .addLabel('Move Tolerance', 'moveTolerance', 180)
41 | .addTextBox('moveTolerance', this.moveTolerance)
42 | .closeHBox()
43 | .closeRow()
44 | .openRow()
45 | .openHBox()
46 | .addLabel('Scale Tolerance', 'scaleTolerance', 180)
47 | .addTextBox('scaleTolerance', this.scaleTolerance)
48 | .closeHBox()
49 | .closeRow()
50 | .create();
51 |
52 | // Handle the cancel
53 | if (ui.dismiss == "cancel") return;
54 |
55 | // Update the settings
56 | this.moveTolerance = settings.moveTolerance = parseFloat(ui.moveTolerance);
57 | this.scaleTolerance = settings.scaleTolerance = parseFloat(ui.scaleTolerance);
58 |
59 | // Save the settings with the update from the dialog
60 | this.saveSettings(settingsPath, settings);
61 |
62 | // get the current timeline
63 | this.preprocess(this.dom.getTimeline());
64 |
65 | fl.showIdleMessage(true);
66 | };
67 |
68 | // Reference to the prototype
69 | var p = OptimizeKeyframes.prototype = new Command();
70 |
71 | /**
72 | * First, a preprocess task will remove any tween where the keyframes
73 | * are the same, this will help eliminate more cases where tweens
74 | * would typically block the optimizing of keyframes
75 | * @method preprocess
76 | */
77 | p.preprocess = function(timeline)
78 | {
79 | var layers = timeline.layers;
80 | var i, j, frames, frame, lastKeyframe;
81 |
82 | for (i = 0; i < layers.length; i++)
83 | {
84 | // Select the current layer so we can remove
85 | // keyframes from it
86 | timeline.currentLayer = i;
87 |
88 | // The collection of frames
89 | frames = layers[i].frames;
90 |
91 | for (j = 0; j < frames.length;)
92 | {
93 | // The current keyframe
94 | frame = frames[j];
95 |
96 | // No content, skip to the next keyframe
97 | if (frame.isEmpty)
98 | {
99 | lastKeyframe = null;
100 | j += frame.duration;
101 | continue;
102 | }
103 |
104 | // Remove tween for frames that are one frame long
105 | if (frame.duration === 1 && frame.tweenType != "none")
106 | {
107 | frame.tweenType = "none";
108 | }
109 |
110 | if (lastKeyframe && // valid last keyframe
111 | lastKeyframe.tweenType != "none" && // previous keyframe is a tween
112 | this.frameCompare(lastKeyframe, frame)) // keyframes are the
113 | {
114 | // Remove the tween for keyframes that are the same
115 | lastKeyframe.tweenType = "none";
116 | }
117 |
118 | // Save the last invalid keyframe
119 | lastKeyframe = frame;
120 |
121 | // go to the next keyframe
122 | j += frame.duration;
123 | }
124 |
125 | // Clear the keyframe for each layer
126 | lastKeyframe = null;
127 | }
128 |
129 | // Remove the keyframes
130 | for (i = 0; i < layers.length; i++)
131 | {
132 | // Select the current layer so we can remove
133 | // keyframes from it
134 | timeline.currentLayer = i;
135 |
136 | // The collection of frames
137 | frames = layers[i].frames;
138 |
139 | for (j = 0; j < frames.length;)
140 | {
141 | // The current keyframe
142 | frame = frames[j];
143 |
144 | // For keyframes that are empty or are the start
145 | // of a tween, then we'll ignore and goto the next
146 | // keyframe
147 | if (frame.tweenType !== "none" || frame.isEmpty)
148 | {
149 | lastKeyframe = null;
150 | j += frame.duration;
151 | continue;
152 | }
153 |
154 | // If the frames are the same
155 | // then we'll remove the current keyframe
156 | if (lastKeyframe && this.frameCompare(lastKeyframe, frame))
157 | {
158 | timeline.clearKeyframes(j);
159 | }
160 | else
161 | {
162 | // Save the last invalid keyframe
163 | lastKeyframe = frame;
164 | }
165 |
166 | // go to the next keyframe
167 | j += frame.duration;
168 | }
169 |
170 | // Clear the keyframe for each layer
171 | lastKeyframe = null;
172 | }
173 | };
174 |
175 | /**
176 | * Compare two keyframes to see if they are the same
177 | * @method frameCompare
178 | * @param {Frame} lastKeyframe The last keyframe to compare to
179 | * @param {Frame} currKeyframe The current keyframe to compare to
180 | * @return {Boolean} True if the keyframes are the same, false if not
181 | */
182 | p.frameCompare = function(lastKeyframe, currKeyframe)
183 | {
184 | // Different element length counts, not the same
185 | if (lastKeyframe.elements.length != currKeyframe.elements.length)
186 | {
187 | return false;
188 | }
189 |
190 | // Loop through all the elements, they should
191 | // be in the same layered order to be considered the same
192 | // so we'll compare an element in the last keyframe
193 | // and a keyframe in the current keyframe
194 | for (var i = 0, len = lastKeyframe.elements.length; i < len; i++)
195 | {
196 | if (!this.elementCompare(lastKeyframe.elements[i], currKeyframe.elements[i]))
197 | {
198 | return false;
199 | }
200 | }
201 | return true;
202 | };
203 |
204 | /**
205 | * Are the elements the same
206 | * @method elementCompare
207 | * @param {Element} element1 First element to compare
208 | * @param {Element} element2 Second element to compare
209 | * @return {Boolean} True if the elements are the same, false if not
210 | */
211 | p.elementCompare = function(element1, element2)
212 | {
213 | var props;
214 |
215 | // The elements should be the same type
216 | if (element1.elementType != element2.elementType)
217 | {
218 | return false;
219 | }
220 | // Compare the positions
221 | else if (!this.matrixCompare(element1.matrix, element2.matrix))
222 | {
223 | return false;
224 | }
225 |
226 | // Special compare instances
227 | if (element1.elementType === "instance")
228 | {
229 | // Library items aren't the same
230 | if (element1.libraryItem !== element2.libraryItem)
231 | {
232 | return false;
233 | }
234 | // Unlikely that these are different
235 | else if (element1.instanceType !== element2.instanceType)
236 | {
237 | return false;
238 | }
239 | // Special checks for symbols types
240 | if (element1.instanceType === "symbol")
241 | {
242 |
243 | props = [
244 | 'symbolType',
245 | 'colorMode',
246 | 'blendMode',
247 | 'loop',
248 | 'slient',
249 | 'is3D',
250 | 'visible',
251 | 'firstFrame',
252 | 'cacheAsBitmap'
253 | ];
254 |
255 | if (!this.propsCompare(element1, element2, props))
256 | {
257 | return false;
258 | }
259 | }
260 | }
261 | return true;
262 | };
263 |
264 | /**
265 | * Compare a list of properties
266 | * @method propsCompare
267 | * @param {Element} element1 First element to compare
268 | * @param {Element} element2 Second element to compare
269 | * @param {Array} props The collectin of properties
270 | * @return {Boolean} True if the elements are the same, false if not
271 | */
272 | p.propsCompare = function(element1, element2, props)
273 | {
274 | for (var i = 0; i < props.length; i++)
275 | {
276 | if (element1[props[i]] !== element2[props[i]])
277 | {
278 | return false;
279 | }
280 | }
281 | return true;
282 | };
283 |
284 | /**
285 | * Method to compare two matrices
286 | * @method matrixCompare
287 | * @param {Matrix} matrix1 First matrix to compare
288 | * @param {Matrix} matrix2 Second matrix to compare
289 | * @return {Boolean} If the matrices are the same
290 | */
291 | p.matrixCompare = function(matrix1, matrix2)
292 | {
293 | return Math.abs(matrix1.a - matrix2.a) <= this.scaleTolerance &&
294 | Math.abs(matrix1.b - matrix2.b) <= this.scaleTolerance &&
295 | Math.abs(matrix1.c - matrix2.c) <= this.scaleTolerance &&
296 | Math.abs(matrix1.d - matrix2.d) <= this.scaleTolerance &&
297 | Math.abs(matrix1.tx - matrix2.tx) <= this.moveTolerance &&
298 | Math.abs(matrix1.ty - matrix2.ty) <= this.moveTolerance;
299 | };
300 |
301 | new OptimizeKeyframes();
302 |
303 | }());
--------------------------------------------------------------------------------
/src/Commands/Optimizing/Optimize Graphics.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * The class for removing unused graphic frames
5 | * @class OptimizeGraphics
6 | */
7 | var OptimizeGraphics = function()
8 | {
9 | /**
10 | * The cache of items that have already been searched
11 | * @property {Array} objectsSearched
12 | */
13 | this.objectsSearched = [];
14 |
15 | /**
16 | * The collection of frames found
17 | * @property {Array} framesFound
18 | */
19 | this.framesFound = [];
20 |
21 | /**
22 | * The specific library item we're searching for
23 | * @property {LibraryItem} searchItem
24 | */
25 | this.searchItem = null;
26 |
27 | /**
28 | * CC-support if the current FLA is an html5canvase
29 | * @property {Boolean} isCanvas
30 | */
31 | this.isCanvas = false;
32 |
33 | /**
34 | * The number of frames in the search item
35 | * @property {int} frameCount
36 | */
37 | this.frameCount = 0;
38 |
39 | // run the constructor
40 | this.initialize();
41 | };
42 |
43 | // Reference to the prototype
44 | var p = OptimizeGraphics.prototype = {};
45 |
46 | /**
47 | * Constructor, start creating the optimize graphics object
48 | * @method initialize
49 | */
50 | p.initialize = function()
51 | {
52 | var i = 0, j = 0, length = 0;
53 |
54 | if ( !fl.getDocumentDOM() )
55 | {
56 | alert("No document opened.");
57 | return;
58 | }
59 |
60 | var doc = fl.getDocumentDOM();
61 | var curTimelines = doc.timelines;
62 | var libraryItems = doc.library.items;
63 | var items = doc.library.getSelectedItems();
64 |
65 | if (items.length === 0)
66 | {
67 | alert("Must select a library item to optimize");
68 | return;
69 | }
70 |
71 | if (doc.type)
72 | {
73 | this.isCanvas = (doc.type == "htmlcanvas");
74 | }
75 | this.searchItem = items[0];
76 | var frameCount = this.frameCount = this.searchItem.timeline.frameCount;
77 |
78 | var con = confirm("You're about to optimize '" + this.searchItem.name + "' by removing unused frames. This may take several minutes, do you want to continue?");
79 |
80 | if (!con)
81 | {
82 | return;
83 | }
84 |
85 | fl.showIdleMessage(false);
86 |
87 | // Clear the output window
88 | fl.outputPanel.clear();
89 | fl.trace("+------------------------------------------+");
90 | fl.trace("| Optimizing '" + this.searchItem.name + "'");
91 | fl.trace("+------------------------------------------+\n");
92 | var now = microtime();
93 |
94 | // Search through the timelines of this document
95 | for(i = 0, length = curTimelines.length; i < length; ++i)
96 | {
97 | this.findSymbolsByTimeline(curTimelines[i]);
98 | }
99 |
100 | // Search through library items not put on stage
101 | for(j = 0, length = libraryItems.length; j < length; ++j)
102 | {
103 | this.searchLibraryItem(libraryItems[j]);
104 | }
105 |
106 | if ( this.framesFound.length === 0 )
107 | {
108 | alert("No frames found in this document.");
109 | }
110 | else
111 | {
112 | this.framesFound.sort(sortNumber);
113 | doc.library.editItem(this.searchItem.name);
114 | var timeline = doc.getTimeline();
115 | for(j=0; j < frameCount; j++)
116 | {
117 | if (!inObject(j, this.framesFound))
118 | {
119 | fl.trace("Clearing frame : " + (j+1));
120 | this.clearFrame(timeline, j);
121 | }
122 | }
123 | }
124 | var sec = Math.round((microtime() - now) * 1000) / 1000;
125 | fl.trace("\nFinished optimizing in " + sec + " sec");
126 |
127 | fl.showIdleMessage(true);
128 | };
129 |
130 | /**
131 | * Get the current time in microtime
132 | * @method microtime
133 | * @private
134 | * @return {Number} Either the Number of seconds or radable seconds
135 | */
136 | var microtime = Date.now ?
137 | function ()
138 | {
139 | return Date.now() / 1000;
140 | } :
141 | function ()
142 | {
143 | return new Date().getTime() / 1000;
144 | };
145 |
146 | /**
147 | * Clear the frame
148 | * @method clearFrame
149 | * @param {Timeline} timeline The timeline object
150 | * @param {int} frameIndex The frame number to clear
151 | */
152 | p.clearFrame = function(timeline, frameIndex)
153 | {
154 | var n = frameIndex;
155 | var layers = timeline.layers;
156 |
157 | for(var i=0, length = layers.length; i < length; i++)
158 | {
159 | var layer = layers[i];
160 | if (layer.layerType != "normal" || !layer.frames[n])
161 | {
162 | continue;
163 | }
164 | var frame = layer.frames[n];
165 | if (frame.elements.length === 0)
166 | {
167 | continue;
168 | }
169 | timeline.setSelectedLayers(i);
170 | if(frame.startFrame + frame.duration > n + 1)
171 | timeline.insertKeyframe(n + 1);
172 | timeline.clearFrames(n, n+1);
173 | }
174 | };
175 |
176 | /**
177 | * Comparator function for sorting
178 | * @private
179 | * @method sortNumber
180 | * @param {Number} a
181 | * @param {Number} b
182 | * @return {Number}
183 | */
184 | var sortNumber = function (a,b)
185 | {
186 | return a - b;
187 | };
188 |
189 | /**
190 | * Find a symbol by the timeline
191 | * @method findSymbolsByTimeline
192 | * @param {Timeline} tLine The timeline to search on
193 | */
194 | p.findSymbolsByTimeline = function(tLine)
195 | {
196 | var j = 0,
197 | jLen = 0,
198 | k = 0,
199 | kLen = 0,
200 | l = 0,
201 | lLen = 0,
202 | el,
203 | frm,
204 | layer,
205 | lyrVisibility;
206 |
207 | var layers = tLine.layers;
208 | // cycle through each of the layers in this timeline
209 | for(j = 0, jLen = layers.length; j < jLen; ++j)
210 | {
211 | // cycle through each of the layers in this timeline
212 | layer = layers[j];
213 |
214 | if (layer.layerType != "normal" || this.framesFound.length == this.frameCount)
215 | {
216 | continue;
217 | }
218 |
219 | // store the layer visibility and then make the layer visible.
220 | // Elements cannot be found on invisible layers
221 | lyrVisibility = layer.visible;
222 | layer.visible = true;
223 |
224 | var frames = layer.frames;
225 | // Loop through the frames
226 | for(k = 0, kLen = frames.length; k < kLen;)
227 | {
228 | // step through the frames on this layer
229 | frm = frames[k];
230 |
231 | // Ignore empty keyframes or non-keyframes
232 | if (frm.elements.length === 0 || this.framesFound.length == this.frameCount)
233 | {
234 | k += frm.duration;
235 | continue;
236 | }
237 |
238 | var elements = frm.elements;
239 | for (l = 0, lLen = elements.length; l < lLen; ++l)
240 | {
241 | // then cycle through the elements on this frame
242 | el = elements[l];
243 |
244 | if (el.elementType == "instance")
245 | {
246 | if (el.libraryItem == this.searchItem)
247 | {
248 | // If we're a movieclip or a button, then select all frames
249 | if (el.symbolType != "graphic")
250 | {
251 | this.addFrameRange(0, this.frameCount);
252 | }
253 | else
254 | {
255 | // If we are loop or play once, we will select a range
256 | if (el.loop == "loop" || el.loop == "play once")
257 | {
258 | this.addFrameRange(el.firstFrame, el.firstFrame + frm.duration, el.loop == "loop");
259 | }
260 | else
261 | {
262 | // Easier is to select first frame
263 | this.addFrame(el.firstFrame);
264 | }
265 | }
266 | }
267 | else
268 | {
269 | // lets get the library item for this element
270 | this.searchLibraryItem(el.libraryItem);
271 | }
272 | }
273 | }
274 | k += frm.duration;
275 | }
276 |
277 | // return this layer to its original visibility (optional)
278 | layer.visible = lyrVisibility;
279 | }
280 | };
281 |
282 | var validSymbolTypes = ["graphic", "movie clip", "button"];
283 |
284 | /**
285 | * Search the library by item
286 | * @method searchLibraryItem
287 | * @param {LibraryItem} libItem The library item to search for
288 | */
289 | p.searchLibraryItem = function(libItem)
290 | {
291 | // Ignore invalid symbol types
292 | if (!inObject(libItem.itemType, validSymbolTypes) || (!this.isCanvas && libItem.linkageImportForRS))
293 | {
294 | this.objectsSearched.push(libItem.name);
295 | return;
296 | }
297 |
298 | // only process new objects and not runtime shared-assets
299 | if ( !inObject(libItem.name, this.objectsSearched))
300 | {
301 | this.objectsSearched.push(libItem.name);
302 |
303 | // get the timeline of this library symbol
304 | libTLine = libItem.timeline;
305 |
306 | if (libTLine)
307 | {
308 | // if there is a timeline, repeat the scan as a recursion
309 | this.findSymbolsByTimeline(libTLine);
310 | }
311 | }
312 | };
313 |
314 | /**
315 | * Add a frame number to the already found frames
316 | * @method addFrame
317 | * @param {int} frameNum The frame number found
318 | */
319 | p.addFrame = function(frameNum)
320 | {
321 | if (!inObject(frameNum, this.framesFound))
322 | {
323 | this.framesFound.push(frameNum);
324 | }
325 | };
326 |
327 | /**
328 | * Add a frame range to already found frames
329 | * @method addFrameRange
330 | * @param {int} startFrame The starting frame index
331 | * @param {int} endFrame The ending frame index
332 | * @param {Boolean} [bLoop=false] If the graphic is set to loop
333 | */
334 | p.addFrameRange = function(startFrame, endFrame, bLoop)
335 | {
336 | var frameCount = this.frameCount;
337 | for(var i=startFrame; i < endFrame; i++)
338 | {
339 | if (i < frameCount)
340 | {
341 | this.addFrame(i);
342 | }
343 | else if (i >= frameCount && bLoop)
344 | {
345 | this.addFrame(i % frameCount);
346 | }
347 | }
348 | };
349 |
350 | /**
351 | * utility function to check if a value is in an object or array
352 | * @method inObject
353 | * @private
354 | * @static
355 | * @param {*} needle The value to check
356 | * @param {Object|Array} haystack The object to check in
357 | * @param {Boolean} If the needle is in the haystack
358 | */
359 | var inObject = function(needle, haystack)
360 | {
361 | for(var k in haystack )
362 | {
363 | if ( haystack[k] == needle ) { return true; }
364 | }
365 | return false;
366 | };
367 |
368 | // Start
369 | new OptimizeGraphics();
370 |
371 | }());
--------------------------------------------------------------------------------
/src/Commands/Optimizing/HTML5 Canvas Size Report.jsfl:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | /**
4 | * Generate a filesize report for HTML Canvas FLAs
5 | * @class FilesizeReport
6 | */
7 | var FilesizeReport = function()
8 | {
9 | var dom = fl.getDocumentDOM();
10 |
11 | if (!dom)
12 | {
13 | alert("No document opened");
14 | return;
15 | }
16 |
17 | // Check for canvas
18 | if (dom.type != "htmlcanvas")
19 | {
20 | alert("Only works with HTML Canvas FLA documents");
21 | return;
22 | }
23 |
24 | // Make sure FLA is saved
25 | if (!dom.pathURI)
26 | {
27 | alert("Must save the document first");
28 | return;
29 | }
30 |
31 | // The folder URI
32 | this.folder = dirname(dom.pathURI);
33 |
34 | // If the FLA is uncompressed use the parent
35 | // folder for relative publish pathing
36 | if (/\.xfl$/i.test(dom.pathURI))
37 | {
38 | this.folder = dirname(this.folder);
39 | }
40 |
41 | // Add the trailing slash
42 | this.folder += "/";
43 |
44 | // Clear output
45 | fl.outputPanel.clear();
46 |
47 | // We need to get some information from the publish profile
48 | // this includes the path to the images, the libs namespace
49 | // and the export file name
50 | var profile = dom.exportPublishProfileString(),
51 | xml = new XML(profile),
52 | properties = xml.PublishProperties,
53 | name;
54 |
55 | for (var i = 0, len = properties.length(); i < len; i++)
56 | {
57 | name = properties[i].attribute('name');
58 | if (name == "JavaScript/HTML")
59 | {
60 | var props = properties.Property;
61 | var filename;
62 | var libNS;
63 |
64 | for (var j = 0, l = props.length(); j < l; j++)
65 | {
66 | name = props[j].attribute('name');
67 |
68 | if (name == "filename") filename = props[j];
69 | if (name == "libNS") libNS = props[j];
70 | }
71 |
72 | // Save the publis file contents
73 | this.publishFile = FLfile.read(this.folder + filename);
74 |
75 | if (!FLfile.exists(this.folder + filename))
76 | {
77 | alert("Publish the FLA before running.");
78 | return;
79 | }
80 | var assets = this.assetReport(filename, libNS);
81 | var media = this.mediaReport(filename);
82 |
83 | logTitle("SUMMARY");
84 |
85 | fl.trace(this.tableLayout([
86 | {
87 | type: "JavaScript Library Assets",
88 | size: filesize(this.totalSize)
89 | },
90 | {
91 | type: "JavaScript Overhead",
92 | size: filesize(this.assetSize - this.totalSize)
93 | },
94 | {
95 | type: "Number of Library Assets",
96 | size: this.numAssets
97 | },
98 | {
99 | type: "External Media Files",
100 | size: filesize(this.mediaSize)
101 | },
102 | {
103 | type: "Total JavaScript & External",
104 | size: filesize(this.mediaSize + this.assetSize)
105 | }], ['type', 'size'])
106 | );
107 |
108 |
109 | logTitle("JAVASCRIPT LIBRARY ASSETS");
110 | fl.trace(assets);
111 |
112 | logTitle("EXTERNAL MEDIA FILES");
113 | fl.trace(media);
114 |
115 | return;
116 | }
117 | }
118 | alert("Unable to find an HTML publishing target");
119 | };
120 |
121 | // Reference to the prototype
122 | var p = FilesizeReport.prototype;
123 |
124 | /**
125 | * Generate a filesize report of media (images/sounds)
126 | * @method mediaReport
127 | * @param {filename} filename The relative or absolute path to export JS file
128 | */
129 | p.mediaReport = function(filename)
130 | {
131 | this.mediaSize = 0;
132 |
133 | var parent = dirname(this.folder + filename);
134 |
135 | var manifestRegExp = /manifest\: (\[[\s\S]*?\])/m;
136 | var match = manifestRegExp.exec(this.publishFile);
137 |
138 | if (match)
139 | {
140 | eval("var manifest = " + match[1]); // jshint ignore:line
141 |
142 | if (manifest && manifest.length)
143 | {
144 | var report = [], file, src;
145 | for (var i = 0; i < manifest.length; i++)
146 | {
147 | file = manifest[i];
148 | src = parent + "/" + manifest[i].src;
149 |
150 | // Ignore files we can't find
151 | if (!FLfile.exists(src)) continue;
152 |
153 | var bytes = FLfile.getSize(src);
154 |
155 | report.push({
156 | file: file.src.substr(file.src.lastIndexOf("/") + 1),
157 | bytes: bytes,
158 | size: filesize(bytes)
159 |
160 | });
161 | this.mediaSize += bytes;
162 | }
163 |
164 | report.sort(sortByBytes);
165 |
166 | return this.tableLayout(report, ['file', 'size']);
167 | }
168 | }
169 | return "";
170 | };
171 |
172 | /**
173 | * Generate a filesize report
174 | * @method assetReport
175 | * @param {filename} filename The relative or absolute path to export JS file
176 | * @param {string} libNS Libraries namespace
177 | */
178 | p.assetReport = function(filename, libNS)
179 | {
180 | // Total file size of export
181 | var assetSize = FLfile.getSize(this.folder + filename);
182 |
183 | // Looking for something that starts with "(lib.* = function(*) {"
184 | // and ends with "p.nominalBounds = new cjs.Rectangle(*);" which
185 | // is the entire library asset.
186 | var assetRegExp = new RegExp("\\(" + libNS + "\\.([\\w\\d\\-]+) \\= function\\([\\w\\,]*\\) \\{[\\s\\S]*?p.nominalBounds \\= (null|new (cjs\\.)?Rectangle\\([\\-\\d\\.\\,]+\\))\\;", "gm");
187 |
188 | // Get just the name of the asset
189 | var assetNameRegExp = new RegExp("^\\(" + libNS + "\\.([\\w\\d\\-]+)");
190 |
191 | // Get the collection of matched assets
192 | var assets = this.publishFile.match(assetRegExp),
193 | report = [],
194 | totalSize = 0,
195 | bytes;
196 |
197 | for (var i = 0; i < assets.length; i++)
198 | {
199 | bytes = assets[i].length;
200 | totalSize += bytes;
201 |
202 | report.push({
203 | name: assetNameRegExp.exec(assets[i])[1],
204 | size: filesize(bytes),
205 | bytes: bytes,
206 | percent: percentage(bytes / assetSize)
207 | });
208 | }
209 |
210 | report.sort(sortByBytes);
211 |
212 | this.assetSize = assetSize;
213 | this.totalSize = totalSize;
214 | this.numAssets = assets.length;
215 |
216 | return this.tableLayout(report, ['name', 'size', 'percent']);
217 | };
218 |
219 | /**
220 | * Get the director path from another path
221 | * @method dirname
222 | * @private
223 | * @param {string} path The incoming path
224 | * @return {string} The base parent folder
225 | */
226 | function dirname(path)
227 | {
228 | return path.replace(/\\/g, '/')
229 | .replace(/\/[^\/]*\/?$/, '');
230 | }
231 |
232 | /**
233 | * Sort function by the bytes size
234 | * @method sortByBytes
235 | * @private
236 | * @param {object} a asset entry
237 | * @param {object} b asset entry
238 | * @return {int} Comparitor output
239 | */
240 | function sortByBytes(a, b)
241 | {
242 | return a.bytes < b.bytes;
243 | }
244 |
245 | /**
246 | * Convert a number to a percentage
247 | * @method
248 | * @private
249 | * @param {number} num A number from 0 - 1
250 | * @return {string} The percentage with % sign
251 | */
252 | function percentage(num)
253 | {
254 | return Math.round(num * 10000) / 100 + "%";
255 | }
256 |
257 | /**
258 | * Make a filesize in bytes readable
259 | * @method filesize
260 | * @private
261 | * @param {int} fileSizeInBytes The bytes
262 | * @return {string} The readable filesize
263 | */
264 | function filesize(fileSizeInBytes)
265 | {
266 | var i = -1;
267 | var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
268 | do {
269 | fileSizeInBytes = fileSizeInBytes / 1024;
270 | i++;
271 | } while (fileSizeInBytes > 1024);
272 | return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
273 | }
274 |
275 | /**
276 | * The amount of padding in the output report
277 | * @property {int} PADDING
278 | * @static
279 | * @readOnly
280 | * @default 4
281 | */
282 | var PADDING = 4;
283 |
284 | /**
285 | * Generate an ASCII table
286 | * @method tableLayout
287 | * @private
288 | * @param {Array} header Collection of header labels
289 | * @param {Array} entries Collection of array entries
290 | * @return {String} The full ASCII table
291 | */
292 | p.tableLayout = function(entries, headers)
293 | {
294 | var finalStr = "";
295 | var layout = {};
296 | var label;
297 |
298 | // Set the minimum values
299 | for (i = 0; i < headers.length; ++i)
300 | {
301 | label = headers[i];
302 | layout[label] = label.length + PADDING;
303 | }
304 |
305 | // Measure the layout for prettier display
306 | for (i = 0; i < entries.length; ++i)
307 | {
308 | for (label in layout)
309 | {
310 | if (label == "contains") continue;
311 |
312 | layout[label] = Math.max(
313 | layout[label],
314 | String(entries[i][label]).length + PADDING
315 | );
316 | }
317 | }
318 |
319 | // Show the table headers
320 | res = "";
321 | var total = 0;
322 | for (label in layout)
323 | {
324 | if (label == "contains") continue;
325 | res += fontLayout(label, layout[label]);
326 | total += layout[label];
327 | }
328 | finalStr += res + "\n";
329 |
330 | // Place a divider
331 | var hr = "";
332 | for (i = 0; i < total; ++i)
333 | {
334 | hr += "-";
335 | }
336 |
337 | finalStr += hr + "\n";
338 |
339 | // Prep the entries
340 | for (i = 0; i < entries.length; ++i)
341 | {
342 | res = "";
343 | for (label in layout)
344 | {
345 | if (label == "contains") continue;
346 | res += fontLayout(String(entries[i][label]), layout[label]);
347 | }
348 | finalStr += res + "\n";
349 | }
350 |
351 | finalStr += hr + "\n";
352 | return "\n" + finalStr;
353 | };
354 |
355 | /**
356 | * Trace out a title fancy style
357 | * @method logTitle
358 | * @private
359 | * @param {string} str The title name
360 | */
361 | var logTitle = function(str)
362 | {
363 | var hr = "";
364 | var size = str.length + PADDING;
365 | while(hr.length < size)
366 | {
367 | hr += "-";
368 | }
369 |
370 | fl.trace("\n+" + hr + "+");
371 | fl.trace("| " + str + " |");
372 | fl.trace("+" + hr + "+");
373 | };
374 |
375 | /**
376 | * Prepend string with text based on a fixed length
377 | * @method fontLayout
378 | * @private
379 | * @param {String} str The string to pad
380 | * @param {int} len The length of the output string
381 | * @return {string} The formatted output string
382 | */
383 | var fontLayout = function(str, len)
384 | {
385 | while(str.length < len)
386 | {
387 | str = str + " ";
388 | }
389 | return str;
390 | };
391 |
392 | // Run command
393 | new FilesizeReport();
394 |
395 | }());
--------------------------------------------------------------------------------
/src/JSFLLibraries/XULWindow.jsfl:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents a XUL panel in JSFL. The object can be set up and then used to invoke a XUL panel without writing an actual XUL file.
3 | * The XML can be generated purely in JSFL and passed in, or you can use convenience methods to add various controls without having
4 | * to writing XML strings in JSFL.
5 | *
6 | *
For full-on XUL reference, the Mozilla document is here:
7 | * https://developer.mozilla.org/en/XUL_Reference . Note that Flash supports a very small subset of this.
8 | *
9 | **/
10 |
11 | /**
12 | * Constructor
13 | * @param title The title of the window. This is only used if you own XML string is not passed in to the create
14 | * method.
15 | * @param buttons The remaining arguments are Strings of types of buttons to include. For convenience, use the static
16 | * constants XULWindow.ACCEPT and XULWindow.CANCEL. This is only used if you own XML string is not passed in to
17 | * the create method.
18 | **/
19 | var XULWindow = function(title)
20 | {
21 | this._title = title || "Options";
22 |
23 | var buttons = [];
24 | var iLen = arguments.length;
25 | for (var i = 1; i < iLen; i++)
26 | {
27 | buttons.push(arguments[i]);
28 | }
29 |
30 | this._columns = false;
31 | this._xml = '';
32 | this._tab = 1;
33 |
34 | /**
35 | * Create muliple columns
36 | * @method columns
37 | * @param {int} cols* The flex amounts of columns
38 | * @return {XULWindow} The instance, for chaining
39 | */
40 | this.columns = function()
41 | {
42 | this._columns = arguments;
43 | return this;
44 | };
45 |
46 | /**
47 | * Creates a XUL window panel. It is attached to the current document
48 | *
49 | * @param xul A String of raw XUL to create. If null, the window will be created according to the various control
50 | * creation methods. If not null, any other method calls would be ignored and the passed-in XUL is used.
51 | * @return An object containing information about the user input. The object will have a property called dismiss
52 | * that contains the name of the button that was clicked (either XULWindow.ACCEPT or
53 | * <code>XULWindow.CANCEL). It will
54 | * also have other properties, named after the various controls' id values. The value contained by these properties
55 | * will be the value associated with the input of the control.
56 | **/
57 | this.create = function(xul)
58 | {
59 | var doc = fl.getDocumentDOM();
60 | if (!doc)
61 | {
62 | alert("There is no open document. XUL Windows need to be attached to a document. [XULWindow::create()]");
63 | return;
64 | }
65 |
66 | var xulFilePath = fl.configURI + escape(this._title) + ".xul";
67 |
68 | // If there's no XUL supplied
69 | if (!xul)
70 | {
71 | // Create the dialog
72 | xul = '';
73 |
74 | // Create the column layout
75 | if (this._columns && this._columns.length > 1)
76 | {
77 | xul += '';
78 |
79 | // Add the columns
80 | for (var i = 0; i < this._columns.length; i++)
81 | {
82 | xul += ' ';
83 | }
84 |
85 | // Add the closing layout plus the dialog contents
86 | xul += ' ' + this._xml + ' ';
87 | }
88 | else
89 | {
90 | // Just add the contents
91 | xul += this._xml;
92 | }
93 | xul += ' ';
94 | }
95 |
96 | FLfile.write(xulFilePath, xul);
97 | var options = fl.getDocumentDOM().xmlPanel(xulFilePath);
98 | FLfile.remove(xulFilePath);
99 |
100 | return options;
101 | };
102 |
103 | /**
104 | * Creates a check box control with a label.
105 | * @method addCheckbox
106 | * @param label String for the label next to the check box.
107 | * @param id String for the id of the checkbox. This is the name of the property on the returned object with data in it
108 | * @param checked Whether initially checked or not.
109 | * @return {XULWindow} The instance, for chaining
110 | */
111 | this.addCheckbox = function(label, id, checked)
112 | {
113 | this._xml += ' ';
114 | return this;
115 | };
116 |
117 | /**
118 | * Start a row element
119 | * @method openRow
120 | * @return {XULWindow} The instance, for chaining
121 | */
122 | this.openRow = function()
123 | {
124 | if (!this._columns)
125 | {
126 | alert("XULWindow::openRow() requires XULWindow::columns() to be set.");
127 | return this;
128 | }
129 | this._xml += '';
130 | return this;
131 | };
132 |
133 | /**
134 | * Close a row element
135 | * @method closeRow
136 | * @return {XULWindow} The instance, for chaining
137 | */
138 | this.closeRow = function()
139 | {
140 | if (!this._columns)
141 | {
142 | alert("XULWindow::closeRow() requires XULWindow::columns() to be set.");
143 | return this;
144 | }
145 | this._xml += '
';
146 | return this;
147 | };
148 |
149 | /**
150 | * Add a spacer
151 | * @method addSpacer
152 | * @return {XULWindow} The instance, for chaining
153 | */
154 | this.addSpacer = function()
155 | {
156 | this._xml += ' ';
157 | return this;
158 | };
159 |
160 | /**
161 | * Add a separator
162 | * @method addSeparator
163 | * @return {XULWindow} The instance, for chaining
164 | */
165 | this.addSeparator = function()
166 | {
167 | this._xml += ' ';
168 | return this;
169 | };
170 |
171 | /**
172 | * Opens an <hbox> nodes so that all controls added after this method call are contained within the hbox. Should
173 | * be ultimately terminated with closeHBox
174 | *
175 | * @see #closeHBox
176 | * @return {XULWindow} The instance, for chaining
177 | */
178 | this.openHBox = function()
179 | {
180 | this._xml += "";
181 | return this;
182 | };
183 |
184 | /**
185 | * Closes a previously open <hbox>, enclosing all intermediate controls within an hbox node.
186 | *
187 | * @see #openHBox
188 | * @return {XULWindow} The instance, for chaining
189 | */
190 | this.closeHBox = function()
191 | {
192 | this._xml += " ";
193 | return this;
194 | };
195 |
196 | /**
197 | * Opens an <vbox> nodes so that all controls added after this method call are contained within the vbox. Should
198 | * be ultimately terminated with closeVBox
199 | * @method openVBox
200 | * @see #closeVBox
201 | * @return {XULWindow} The instance, for chaining
202 | */
203 | this.openVBox = function()
204 | {
205 | this._xml += "";
206 | return this;
207 | };
208 |
209 | /**
210 | * Closes a previously open <vbox>, enclosing all intermediate controls within an vbox node.
211 | * @method closeVBox
212 | * @see #openVBox
213 | * @return {XULWindow} The instance, for chaining
214 | */
215 | this.closeVBox = function()
216 | {
217 | this._xml += " ";
218 | return this;
219 | };
220 |
221 | /**
222 | * @private
223 | * Adds a button to the panel, but I assume there might be more involved in getting one to do something than we can realistically
224 | * accomplish here.
225 | * @method addButton
226 | * @return {XULWindow} The instance, for chaining
227 | */
228 | this.addButton = function(label, id, accessKey, autocheck)
229 | {
230 | this._xml += ' ';
231 | return this;
232 | };
233 |
234 | /**
235 | * Creates a List Box.
236 | * @method addListBox
237 | * @param id The name of the property on the returned object with data in it.
238 | * @param items Array of Objects specifying the items to display. The Objects should take on the following form:
239 | * {label:"Text to display", value:"valueOfThisItem", selected:true}
240 | * label is required. If value is omitted, the label is returned
241 | * as the selected value.
242 | * @param width The width in pixels. Defaults to 200.
243 | * @param rows The number of rows to display in the viewable area. If the rows is less than the number of
244 | * items in the list, scrollbars will appear. If this parameter is omitted, the length of the Array passed
245 | * in to items is used.
246 | * @return {XULWindow} The instance, for chaining
247 | */
248 | this.addListBox = function(id, items, width, rows)
249 | {
250 | var signature = "addListBox(id:String, items:Array, width:Number=NaN, rows:Number=NaN)";
251 |
252 | if (!id)
253 | {
254 | alert("XULWindow::addListBox() requires an id parameter.\n\t" + signature);
255 | return this;
256 | }
257 |
258 | if (!items)
259 | {
260 | alert("XULWindow::addListBox() requires an items parameter.\n\t" + signature);
261 | return this;
262 | }
263 |
264 | if (isNaN(width)) { width = 200; }
265 | if (isNaN(rows)) { rows = items.length; }
266 |
267 | this._xml += '\n';
268 | var iLen = items.length;
269 | var item;
270 | var selected;
271 | var value;
272 |
273 | for (var i = 0; i < iLen; i++)
274 | {
275 | item = items[i];
276 |
277 | if (!item.label)
278 | {
279 | alert("The items parameter of XULWindow::addListBox() needs to be an Array of Objects, each Object consisting of" +
280 | " at least a label property, and optionally a value and a selected property.");
281 | return this;
282 | }
283 |
284 | value = item.value ? 'value="'+item.value+'" ' : '';
285 | selected = item.selected ? 'selected="'+item.selected+'" ' : '';
286 |
287 | this._xml += '\t \n';
288 | }
289 |
290 | this._xml += " \n";
291 | return this;
292 | };
293 |
294 | /**
295 | * Adds a basic text label.
296 | * @method addLabel
297 | * @param label The text to display in the label.
298 | * @param control The id of the associated control. If the user clicks on the label, focus is moved to the control. (Doesn't work?)
299 | * @return {XULWindow} The instance, for chaining
300 | */
301 | this.addLabel = function(label, control, width)
302 | {
303 | this._xml += ' \n';
304 | return this;
305 | };
306 |
307 | /**
308 | * Adds a text box in which the user can type.
309 | * @method addTextBox
310 | * @param id The name of the property on the returned data object.
311 | * @param value The default value contained within the text box.
312 | * @return {XULWindow} The instance, for chaining
313 | */
314 | this.addTextBox = function(id, value, width)
315 | {
316 | this._xml += ' ';
317 | return this;
318 | };
319 |
320 | /**
321 | * A a popup menu
322 | * @method addMenuList
323 | * @param {String} id The id of the list
324 | * @param {Array} items The collection of items to add
325 | * @param {String|Number} [selectValue] The value to select
326 | * @param {Number} [width] The width of the menu list
327 | * @return {XULWindow} The instance, for chaining
328 | */
329 | this.addMenuList = function(id, items, selectValue, width)
330 | {
331 | this._xml += '';
332 | var iLen = items.length, value, i, selected;
333 | for (i = 0; i < iLen; i++)
334 | {
335 | value = items[i].value ? 'value="'+items[i].value+'" ' : '';
336 | selected = (selectValue == items[i].value) ? selected=' selected="true"' : '';
337 | this._xml += '\t \n';
338 | }
339 | this._xml += ' '+"\n";
340 | return this;
341 | };
342 |
343 | /**
344 | * Creates a vertically-oriented group of radio buttons.
345 | * @method addRadioGroup
346 | * @param id The name of the property on the returned object with data in it.
347 | * @param items Array of Objects specifying the buttons to display. The Objects should take on the following form:
348 | * {label:"Text to display", value:"valueOfThisButton", selected:true}
349 | * label is required. If value is omitted, the label is returned
350 | * as the selected value.
351 | * @return {XULWindow} The instance, for chaining
352 | */
353 | this.addRadioGroup = function(id, items)
354 | {
355 | var signature = "addRadioGroup(id:String, items:Array)";
356 |
357 | if (!id)
358 | {
359 | alert("XULWindow::addRadioGroup() requires an id parameter.\n\t" + signature);
360 | return this;
361 | }
362 |
363 | if (!items)
364 | {
365 | alert("XULWindow::addRadioGroup() requires an items parameter.\n\t" + signature);
366 | return this;
367 | }
368 |
369 | this._xml += '\n';
370 |
371 | var iLen = items.length;
372 | var item;
373 | var selected;
374 | var value;
375 | for (var i = 0; i < iLen; i++)
376 | {
377 | item = items[i];
378 |
379 | if (!item.label)
380 | {
381 | alert("The items parameter of XULWindow::addRadioGroup() needs to be an Array of Objects, each Object consisting of" +
382 | " at least a label property, and optionally a value and a selected property.");
383 | return this;
384 | }
385 |
386 | value = item.value ? 'value="'+item.value+'" ' : '';
387 | selected = item.selected ? 'selected="'+item.selected+'" ' : '';
388 |
389 | this._xml += '\t \n';
390 | }
391 |
392 | this._xml += " \n";
393 |
394 | return this;
395 | };
396 | };
397 |
398 | /**
399 | * Static constant alias for the name of the "accept" button.
400 | **/
401 | XULWindow.__defineGetter__("ACCEPT", function(){ return "accept"; });
402 | /**
403 | * Static constant alias for the name of the "cancel" button.
404 | **/
405 | XULWindow.__defineGetter__("CANCEL", function(){ return "cancel"; });
406 |
--------------------------------------------------------------------------------