diff --git a/examples/Node.js/Tadpoles.js b/examples/Node.js/Tadpoles.js index 47ed04d6..6b025864 100644 --- a/examples/Node.js/Tadpoles.js +++ b/examples/Node.js/Tadpoles.js @@ -1,7 +1,7 @@ -require('paper'); -var paper = require('./Tadpoles.pjs'); +var paper = require('paper'); +var scope = require('./Tadpoles.pjs')(new paper.Size(1024, 768)); -paper.view.exportFrames({ +scope.view.exportFrames({ amount: 400, directory: __dirname, onComplete: function() { diff --git a/examples/Node.js/Tadpoles.pjs b/examples/Node.js/Tadpoles.pjs index 6db5b820..3b02c84d 100644 --- a/examples/Node.js/Tadpoles.pjs +++ b/examples/Node.js/Tadpoles.pjs @@ -1,5 +1,3 @@ -paper.setup(new Canvas(1024, 768)); - // Adapted from Flocking Processing example by Daniel Schiffman: // http://processing.org/learning/topics/flocking.html diff --git a/package.json b/package.json index 44a6995c..f7063eb3 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "uglify-js": "~2.3.6", - "prepro": "~0.8.0", + "prepro": "~0.8.1", "grunt": "~0.4.1", "grunt-contrib-uglify": "~0.2.2" }, diff --git a/src/core/PaperScope.js b/src/core/PaperScope.js index 00a233c6..5061f7f0 100644 --- a/src/core/PaperScope.js +++ b/src/core/PaperScope.js @@ -102,16 +102,9 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ /** * The reference to the active tool. + * @name PaperScope#tool * @type Tool - * @bean */ - getTool: function() { - // If no tool exists yet but one is requested, produce it now on the fly - // so it can be used in PaperScript. - if (!this._tool) - this._tool = new Tool(); - return this._tool; - }, /** * The list of available tools. @@ -131,10 +124,9 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ return this; }, - evaluate: function(code) { - var res = paper.PaperScript.evaluate(code, this); + execute: function(code) { + paper.PaperScript.execute(code, this); View.updateFocus(); - return res; }, /** @@ -166,10 +158,10 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ // Copy over all fields from this scope to the destination. // Do not use Base.each, since we also want to enumerate over // fields on PaperScope.prototype, e.g. all classes - for (var key in this) { - if (!/^(version|_id)/.test(key)) + for (var key in this) + // Exclude all 'hidden' fields + if (!/^_/.test(key)) scope[key] = this[key]; - } }, /** @@ -228,14 +220,14 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{ _id: 0, /** - * Retrieves a PaperScope object with the given id or associated with - * the passed canvas element. + * Retrieves a PaperScope object with the given id or associated + * with the passed canvas element. * * @param id */ get: function(id) { // If a script tag is passed, get the id from it. - if (typeof id === 'object') + if (id && id.getAttribute) id = id.getAttribute('id'); return this._scopes[id] || null; }, diff --git a/src/core/PaperScript.js b/src/core/PaperScript.js index 5e06e5f8..e87c8aef 100644 --- a/src/core/PaperScript.js +++ b/src/core/PaperScript.js @@ -14,19 +14,10 @@ * @name PaperScript * @namespace */ -// Note that due to the use of with(), PaperScript gets compiled outside the -// main paper scope, and is added to the PaperScope class. This allows for -// better minification and the future use of strict mode once it makes sense -// in terms of performance. -paper.PaperScope.prototype.PaperScript = (function(root) { - var Base = paper.Base, - PaperScope = paper.PaperScope, - // For local reference, for now only when setting lineNumberBase on - // Firefox. - PaperScript, - // Locally turn of exports and define for inlined acorn / esprima. - // Just declaring the local vars is enough, as they will be undefined. - exports, define, +var PaperScript = Base.exports.PaperScript = (function(root) { + // Locally turn of exports and define for inlined acorn / esprima. + // Just declaring the local vars is enough, as they will be undefined. + var exports, define, // The scope into which the library is loaded. scope = this; /*#*/ if (__options.version == 'dev') { @@ -69,9 +60,9 @@ paper.PaperScope.prototype.PaperScript = (function(root) { }, {} ); - paper.Point.inject(fields); - paper.Size.inject(fields); - paper.Color.inject(fields); + Point.inject(fields); + Size.inject(fields); + Color.inject(fields); // Use very short name for the binary operator (_$_) as well as the // unary operator ($_), as operations will be replaced with then. @@ -238,97 +229,117 @@ paper.PaperScope.prototype.PaperScript = (function(root) { } /** - * Evaluates parsed PaperScript code in the passed {@link PaperScope} - * object. It also installs handlers automatically for us. + * Executes the parsed PaperScript code in a compiled function that receives + * all properties of the passed {@link PaperScope} as arguments, to emulate + * a global scope with unaffected performance. It also installs global view + * and tool handlers automatically for you. * - * @name PaperScript.evaluate + * @name PaperScript.execute * @function * @param {String} code The PaperScript code - * @param {PaperScript} scope The scope in which the code is executed - * @return {Object} the result of the code evaluation + * @param {PaperScript} scope The scope for which the code is executed */ - function evaluate(code, scope) { + function execute(code, scope) { // Set currently active scope. paper = scope; - var view = scope.project && scope.project.view, + var view = scope.getView(), + // Only create a tool object if something resembling a tool handler + // definition is contained in the code. + tool = /\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code) + ? new Tool() + : null, + toolHandlers = tool ? tool._events : [], + // Compile a list of all handlers that can be defined globally + // inside the PaperScript. These are passed on to the function as + // undefined arguments, so that their name exists, rather than + // injecting a code line that defines them as variables. + // They are exported again at the end of the function. + handlers = ['onFrame', 'onResize'].concat(toolHandlers), res; - // Define variables for potential handlers, so eval() calls below to - // fetch their values do not require try-catch around them. - // Use with() {} in order to make the scope the current 'global' scope - // instead of window. - with (scope) { - // Within this, use a function scope, so local variables to not try - // and set themselves on the scope object. - (function() { - var onActivate, onDeactivate, onEditOptions, - onMouseDown, onMouseUp, onMouseDrag, onMouseMove, - onKeyDown, onKeyUp, onFrame, onResize; - code = compile(code); -/*#*/ if (__options.environment == 'browser') { - if (root.InstallTrigger) { // Firefox - // On Firefox, all error numbers inside evaled code are - // relative to the line where the eval happened. Totally - // silly, but that's how it is. So we're calculating the - // base of lineNumbers, to remove it again from reported - // errors. Luckily, Firefox is the only browser where we can - // define the lineNumber for exceptions. - var handle = PaperScript.handleException; - if (!handle) { - handle = PaperScript.handleException = function(e) { - throw e.lineNumber >= lineNumber - ? new Error(e.message, e.fileName, - e.lineNumber - lineNumber) - : e; - } - // We're using a crazy hack to detect wether the library - // is minified or not: By generating a second error on - // the 2nd line and using the difference in line numbers - // to calculate the offset to the eval, it works in both - // casees. - var lineNumber = new Error().lineNumber; - lineNumber += (new Error().lineNumber - lineNumber) * 3; - } - try { - // Add a semi-colon at the start so Firefox doesn't - // swallow empty lines and shift error messages. - res = eval(';' + code); - } catch (e) { - handle(e); - } - } else { - res = eval(code); - } -/*#*/ } else { // !__options.environment == 'browser' - res = eval(code); -/*#*/ } // !__options.environment == 'browser' - // Only look for tool handlers if something resembling their - // name is contained in the code. - if (/on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)) { - Base.each(paper.Tool.prototype._events, function(key) { - var value = eval(key); - if (value) { - // Use the getTool accessor that handles auto tool - // creation for us: - scope.getTool()[key] = value; - } - }); - } - if (view) { - view.setOnResize(onResize); - // Fire resize event directly, so any user - // defined resize handlers are called. - view.fire('resize', { - size: view.size, - delta: new Point() - }); - if (onFrame) - view.setOnFrame(onFrame); - // Automatically update view at the end. - view.update(); - } - }).call(scope); + code = compile(code); + // compile a list of paramter names for all variables that need to + // appear as globals inside the script. At the same time, also collect + // their values, so we can pass them on as arguments in the function + // call. + var params = ['_$_', '$_', 'view', 'tool'], + args = [_$_, $_ , view, tool]; + // Look through all enumerable properties on the scope and expose these + // too as pseudo-globals. + for (var key in scope) { + if (!/^_/.test(key)) { + params.push(key); + args.push(scope[key]); + } + } + // Finally define the handler variable names as parameters and compose + // the string describing the properties for the returned object at the + // end of the code execution, so we can retrieve their values from the + // function call. + handlers = Base.each(handlers, function(key) { + params.push(key); + this.push(key + ': ' + key); + }, []).join(', '); + // We need an additional line that returns the handlers in one object. + code += '\nreturn { ' + handlers + ' };'; +/*#*/ if (__options.environment == 'browser') { + if (root.InstallTrigger) { // Firefox + // Add a semi-colon at the start so Firefox doesn't swallow empty + // lines and shift error messages. + code = ';' + code; + // On Firefox, all error numbers inside evaled code are relative to + // the line where the eval happened. Totally silly, but that's how + // it is. So we're calculating the base of lineNumbers, to remove it + // again from reported errors. Luckily, Firefox is the only browser + // where we can define the lineNumber for exceptions. + var handle = PaperScript.handleException; + if (!handle) { + handle = PaperScript.handleException = function(e) { + throw e.lineNumber >= lineNumber + ? new Error(e.message, e.fileName, + e.lineNumber - lineNumber) + : e; + }; + // We're using a crazy hack to detect wether the library is + // minified or not: By generating a second error on the 2nd line + // and using the difference in line numbers to calculate the + // offset to the eval, it works in both casees. + var lineNumber = new Error().lineNumber; + lineNumber += (new Error().lineNumber - lineNumber) * 3; + } + try { + res = new Function(params, code).apply(scope, args); + // NOTE: in order for the calculation of the above lineNumber + // offset to work, we cannot add any statements before the above + // line of code, nor can we put it into a separate function. + } catch (e) { + handle(e); + } + } else { + res = new Function(params, code).apply(scope, args); + } +/*#*/ } else { // !__options.environment == 'browser' + res = new Function(params, code).apply(scope, args); +/*#*/ } // !__options.environment == 'browser' + // Now install the 'global' tool and view handlers, and we're done! + Base.each(toolHandlers, function(key) { + var value = res[key]; + if (value) + tool[key] = value; + }); + if (view) { + if (res.onResize) + view.setOnResize(res.onResize); + // Fire resize event directly, so any user + // defined resize handlers are called. + view.fire('resize', { + size: view.size, + delta: new Point() + }); + if (res.onFrame) + view.setOnFrame(res.onFrame); + // Automatically update view at the end. + view.update(); } - return res; } /*#*/ if (__options.environment == 'browser') { @@ -356,12 +367,12 @@ paper.PaperScope.prototype.PaperScript = (function(root) { if (src) { // If we're loading from a source, request that first and // then run later. - paper.Http.request('get', src, function(code) { - evaluate(code, scope); + Http.request('get', src, function(code) { + execute(code, scope); }); } else { // We can simply get the code form the script tag. - evaluate(script.innerHTML, scope); + execute(script.innerHTML, scope); } // Mark script as loaded now. script.setAttribute('data-paper-ignore', true); @@ -375,12 +386,12 @@ paper.PaperScope.prototype.PaperScript = (function(root) { // Handle it asynchronously setTimeout(load); } else { - paper.DomEvent.add(window, { load: load }); + DomEvent.add(window, { load: load }); } - return PaperScript = { + return { compile: compile, - evaluate: evaluate, + execute: execute, load: load, lineNumberBase: 0 }; @@ -393,22 +404,28 @@ paper.PaperScope.prototype.PaperScript = (function(root) { path = require('path'); require.extensions['.pjs'] = function(module, uri) { - var source = compile(fs.readFileSync(uri, 'utf8')), - scope = new PaperScope(); - scope.__filename = uri; - scope.__dirname = path.dirname(uri); - // Expose core methods and values - scope.require = require; - scope.console = console; - evaluate(source, scope); - module.exports = scope; + // Requiring a PaperScript on Node.js returns an initialize method which + // needs to receive a Canvas object when called and returns the + // PaperScope. + module.exports = function(canvas) { + var source = compile(fs.readFileSync(uri, 'utf8')), + scope = new PaperScope(); + scope.setup(canvas); + scope.__filename = uri; + scope.__dirname = path.dirname(uri); + // Expose core methods and values + scope.require = require; + scope.console = console; + execute(source, scope); + return scope; + }; }; /*#*/ } // __options.environment == 'node' - return PaperScript = { + return { compile: compile, - evaluate: evaluate + execute: execute }; /*#*/ } // !__options.environment == 'browser' diff --git a/src/docs/global.js b/src/docs/global.js index ee4d7b49..979ea441 100644 --- a/src/docs/global.js +++ b/src/docs/global.js @@ -10,16 +10,19 @@ * All rights reserved. */ -/** @scope _global_ */ { - -// DOCS: Find a way to put this description into _global_ - -/** - * In a PaperScript context, the global scope is populated with all - * fields of the currently active {@link PaperScope} object. In a JavaScript - * context, it only contains the {@link #paper} reference to the currently - * active {@link PaperScope} object, which also exposes all Paper classes. - */ + /** + * @name _global_ + * @namespace + * + * When code is executed as PaperScript, the script's scope is populated with + * all fields of the currently active {@link PaperScope} object, which within + * the script appear to be global. + * + * In a JavaScript context, only the {@link paper} variable is added to the + * global scope, referencing the currently active {@link PaperScope} object, + * through which all properties and Paper.js classes can be accessed. + */ +/** @scope _global_ */{ /** * A reference to the currently active {@link PaperScope} object. @@ -32,40 +35,58 @@ // DOCS: This does not work: @borrows PaperScope#version as _global_#version, // so we're repeating documentation here form PaperScope: /** - * {@grouptitle Global PaperScope Properties (for PaperScript)} + * {@grouptitle Global PaperScript Properties} + * + * The project for which the PaperScript is executed. + * + * Note that when working with mulitple projects, this does not necessarily + * reflect the currently active project. For this, use + * {@link PaperScope#project} instead. * - * The currently active project. * @name project * @type Project */ /** * The list of all open projects within the current Paper.js context. + * * @name projects * @type Project[] */ /** - * The reference to the active project's view. + * The reference to the project's view. + * + * Note that when working with mulitple projects, this does not necessarily + * reflect the view of the currently active project. For this, use + * {@link PaperScope#view} instead. + * * @name view * @type View */ /** - * The reference to the active tool. + * The reference to the tool object which is automatically created when global + * tool event handlers are defined. + * + * Note that when working with mulitple tools, this does not necessarily + * reflect the currently active tool. For this, use {@link PaperScope#tool} + * instead. + * * @name tool * @type Tool */ /** * The list of available tools. + * * @name tools * @type Tool[] */ /** - * {@grouptitle View Event Handlers (for PaperScript)} - * A reference to the {@link View#onFrame} handler function. + * {@grouptitle PaperScript View Event Handlers} + * A global reference to the {@link View#onFrame} handler function. * * @name onFrame * @property @@ -81,7 +102,7 @@ */ /** - * {@grouptitle Mouse Event Handlers (for PaperScript)} + * {@grouptitle PaperScript Tool Event Handlers} * A reference to the {@link Tool#onMouseDown} handler function. * @name onMouseDown * @property diff --git a/src/paper.js b/src/paper.js index 5f1b4ced..3b695514 100644 --- a/src/paper.js +++ b/src/paper.js @@ -138,12 +138,10 @@ var paper = new function(undefined) { /*#*/ include('svg/SVGImport.js'); /*#*/ } // __options.svg -/*#*/ include('export.js'); -return paper; -}; - -// include PaperScript separately outside the main paper scope, due to its use -// of with(). This also simplifies making its inclusion optional. /*#*/ if (__options.paperscript) { /*#*/ include('core/PaperScript.js'); /*#*/ } // __options.paperscript + +/*#*/ include('export.js'); +return paper; +}; diff --git a/src/tool/Tool.js b/src/tool/Tool.js index 2a803ce5..2f72c55e 100644 --- a/src/tool/Tool.js +++ b/src/tool/Tool.js @@ -45,7 +45,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{ _class: 'Tool', _list: 'tools', - _reference: '_tool', // PaperScope has accessor for #tool + _reference: 'tool', _events: [ 'onActivate', 'onDeactivate', 'onEditOptions', 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', 'onKeyDown', 'onKeyUp' ], diff --git a/src/ui/Key.js b/src/ui/Key.js index 0543a465..db55cda6 100644 --- a/src/ui/Key.js +++ b/src/ui/Key.js @@ -74,7 +74,7 @@ var Key = new function() { type = down ? 'keydown' : 'keyup', view = View._focused, scope = view && view.isVisible() && view._scope, - tool = scope && scope._tool, + tool = scope && scope.tool, name; keyMap[key] = down; // Detect modifiers and mark them as pressed / released diff --git a/src/ui/View.js b/src/ui/View.js index 077785c6..db0b8969 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -664,7 +664,7 @@ var View = Base.extend(Callback, /** @lends View# */{ // Always first call the view's mouse handlers, as required by // CanvasView, and then handle the active tool, if any. view._handleEvent('mousedown', point, event); - if (tool = view._scope._tool) + if (tool = view._scope.tool) tool._handleEvent('mousedown', point, event); // In the end we always call update(), which only updates the view if // anything has changed in the above calls. @@ -673,7 +673,7 @@ var View = Base.extend(Callback, /** @lends View# */{ function handleMouseMove(view, point, event) { view._handleEvent('mousemove', point, event); - var tool = view._scope._tool; + var tool = view._scope.tool; if (tool) { // If there's no onMouseDrag, fire onMouseMove while dragging. tool._handleEvent(dragging && tool.responds('mousedrag')