Merge branch 'unified-version' into develop

This commit is contained in:
Jürg Lehni 2016-01-26 21:06:54 +01:00
commit 9ad63a7231
34 changed files with 459 additions and 13870 deletions

13375
dist/paper-core.js vendored

File diff suppressed because it is too large Load diff

1
dist/paper-core.js vendored Symbolic link
View file

@ -0,0 +1 @@
../src/load.js

1
dist/paper-node.js vendored
View file

@ -1 +0,0 @@
../src/load.js

View file

@ -1,5 +1,5 @@
var paper = require('paper'); var paper = require('paper');
paper.setup(new paper.Canvas(1024, 768)); paper.setup(new paper.Size(1024, 768));
var layer = paper.project.activeLayer; var layer = paper.project.activeLayer;

View file

@ -2,7 +2,7 @@ var http = require('http');
var paper = require('paper'); var paper = require('paper');
http.createServer(function(request, response) { http.createServer(function(request, response) {
var canvas = new paper.Canvas(800, 800); var canvas = paper.createCanvas(800, 800);
paper.setup(canvas); paper.setup(canvas);
with(paper) { with(paper) {
var style = { var style = {
@ -30,4 +30,4 @@ http.createServer(function(request, response) {
}); });
}).listen(3000); }).listen(3000);
console.log('Server running at http://127.0.0.1:3000/'); console.log('Server running at http://127.0.0.1:3000/');

View file

@ -2,12 +2,12 @@ var paper = require('paper'),
path = require('path'), path = require('path'),
fs = require('fs'); fs = require('fs');
var canvas = new paper.Canvas(612, 792, 'pdf'); var canvas = paper.createCanvas(612, 792, 'pdf');
paper.setup(canvas); paper.setup(canvas);
with (paper) { with (paper) {
fs.readFile('./in.json', { encoding: 'utf8' }, function (err, data) { fs.readFile('./in.json', { encoding: 'utf8' }, function (err, data) {
if (err) if (err)
throw err; throw err;
project.importJSON(data); project.importJSON(data);
view.update(); view.update();
fs.writeFile(path.resolve(__dirname, 'out.pdf'), canvas.toBuffer(), function (err) { fs.writeFile(path.resolve(__dirname, 'out.pdf'), canvas.toBuffer(), function (err) {

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
var paper = require('paper'); var paper = require('paper');
var fs = require('fs'); var fs = require('fs');
var canvas = new paper.Canvas(800, 600); var canvas = paper.createCanvas(800, 600);
paper.setup(canvas); paper.setup(canvas);
var url = 'http://assets.paperjs.org/images/marilyn.jpg'; var url = 'http://assets.paperjs.org/images/marilyn.jpg';
@ -13,7 +13,7 @@ raster.onLoad = function() {
// Saving the canvas to a file. // Saving the canvas to a file.
out = fs.createWriteStream(__dirname + '/canvas.png'); out = fs.createWriteStream(__dirname + '/canvas.png');
stream = canvas.pngStream(); stream = canvas.createPNGStream();
stream.on('data', function(chunk) { stream.on('data', function(chunk) {
out.write(chunk); out.write(chunk);

View file

@ -2,8 +2,8 @@ var paper = require('paper'),
path = require('path'), path = require('path'),
fs = require('fs'); fs = require('fs');
paper.setup(new paper.Canvas(300, 600));
with (paper) { with (paper) {
paper.setup(new Size(300, 600));
var stops = [new Color(1, 1, 0, 0), 'red', 'black']; var stops = [new Color(1, 1, 0, 0), 'red', 'black'];
var radius = view.bounds.width * 0.4, var radius = view.bounds.width * 0.4,

View file

@ -2,12 +2,9 @@ var paper = require('paper'),
path = require('path'), path = require('path'),
fs = require('fs'); fs = require('fs');
paper.setup(new paper.Canvas(300, 600)); paper.setup(new paper.Size(300, 600));
with (paper) { paper.project.importSVG('file://' + path.resolve(__dirname, 'in.svg'), {
fs.readFile('./in.svg', { encoding: 'utf8' }, function (err, data) { onLoad: function(item) {
if (err)
throw err;
project.importSVG(data);
paper.view.exportFrames({ paper.view.exportFrames({
amount: 1, amount: 1,
directory: __dirname, directory: __dirname,
@ -18,5 +15,5 @@ with (paper) {
console.log(event.percentage + '% complete, frame took: ' + event.delta); console.log(event.percentage + '% complete, frame took: ' + event.delta);
} }
}); });
}); }
} });

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,10 @@
var scale = (Math.sin(event.time * 2) + 1) / 2; var scale = (Math.sin(event.time * 2) + 1) / 2;
raster.scale(scale / lastScale); raster.scale(scale / lastScale);
lastScale = scale; lastScale = scale;
raster.position = center + [Math.sin(event.time * 3) * 256, Math.sin(event.time * 2.5) * 256]; raster.position = center + [
Math.sin(event.time * 3) * 256,
Math.sin(event.time * 2.5) * 256
];
raster.rotate(event.delta * 120); raster.rotate(event.delta * 120);
} }
</script> </script>

View file

@ -23,8 +23,7 @@ var gulp = require('gulp'),
// object, merged in with the options required above. // object, merged in with the options required above.
var buildOptions = { var buildOptions = {
full: { paperScript: true }, full: { paperScript: true },
core: { paperScript: false }, core: { paperScript: false }
node: { environment: 'node', paperScript: true }
}; };
var buildNames = Object.keys(buildOptions); var buildNames = Object.keys(buildOptions);
@ -32,9 +31,13 @@ var buildNames = Object.keys(buildOptions);
gulp.task('build', gulp.task('build',
buildNames.map(function(name) { buildNames.map(function(name) {
return 'build:' + name; return 'build:' + name;
}) }).concat(['build:copy'])
); );
gulp.task('build:copy', function() {
gulp.src(['src/node/*.js']).pipe(gulp.dest('dist/node'));
});
buildNames.forEach(function(name) { buildNames.forEach(function(name) {
gulp.task('build:' + name, ['clean:build:' + name, 'minify:acorn'], function() { gulp.task('build:' + name, ['clean:build:' + name, 'minify:acorn'], function() {
return gulp.src('src/paper.js') return gulp.src('src/paper.js')

View file

@ -17,12 +17,9 @@ var gulp = require('gulp'),
gulp.task('load', ['clean:load'], function() { gulp.task('load', ['clean:load'], function() {
return gulp.src('src/load.js') return gulp.src('src/load.js')
.pipe(symlink('dist/paper-full.js')) .pipe(symlink('dist/paper-full.js'))
.pipe(symlink('dist/paper-node.js')); .pipe(symlink('dist/paper-core.js'));
}); });
gulp.task('clean:load', function() { gulp.task('clean:load', function() {
return del([ return del([ 'dist/paper-full.js', 'dist/paper-core.js', 'dist/node/**' ]);
'dist/paper-full.js',
'dist/paper-node.js'
]);
}); });

View file

@ -13,8 +13,7 @@
"Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)", "Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)",
"Jonathan Puckey <jonathan@studiomoniker.com> (http://studiomoniker.com)" "Jonathan Puckey <jonathan@studiomoniker.com> (http://studiomoniker.com)"
], ],
"main": "dist/paper-node.js", "main": "dist/paper-full.js",
"browser": "dist/paper-full.js",
"scripts": { "scripts": {
"lint": "jshint src" "lint": "jshint src"
}, },
@ -29,12 +28,11 @@
"node": ">=0.8.0 <5.0.0" "node": ">=0.8.0 <5.0.0"
}, },
"dependencies": { "dependencies": {
"jsdom": ">=3.1.2 <8.0.0", "jsdom": "git://github.com/lehni/jsdom.git#3d55789d0f4d55392721b1e22890837fde472375",
"mime": "^1.3.0", "source-map-support": "^0.4.0"
"request": "^2.61.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"canvas": "^1.2.9" "canvas": "^1.3.5"
}, },
"devDependencies": { "devDependencies": {
"acorn": "~0.5.0", "acorn": "~0.5.0",

View file

@ -25,12 +25,8 @@ var CanvasProvider = {
if (this.canvases.length) { if (this.canvases.length) {
canvas = this.canvases.pop(); canvas = this.canvases.pop();
} else { } else {
/*#*/ if (__options.environment == 'browser') {
canvas = document.createElement('canvas'); canvas = document.createElement('canvas');
/*#*/ } else { // __options.environment != 'browser' clear = false; // It's already cleared through createElement().
canvas = new Canvas(width, height);
clear = false; // It's already cleared through constructor.
/*#*/ } // __options.environment != 'browser'
} }
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
// If they are not the same size, we don't need to clear them // If they are not the same size, we don't need to clear them

View file

@ -71,48 +71,40 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
}; };
CanvasProvider.release(ctx); CanvasProvider.release(ctx);
} }
if (!this.agent) {
/*#*/ if (__options.environment == 'browser') { var user = window.navigator.userAgent.toLowerCase(),
if (!this.browser) {
var agent = navigator.userAgent.toLowerCase(),
// Detect basic platforms, only mac internally required for now. // Detect basic platforms, only mac internally required for now.
platform = (/(win)/.exec(agent) os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0],
|| /(mac)/.exec(agent) platform = os === 'darwin' ? 'mac' : os,
|| /(linux)/.exec(agent) agent = proto.agent = proto.browser = { platform: platform };
|| [])[0],
browser = proto.browser = { platform: platform };
if (platform) if (platform)
browser[platform] = true; agent[platform] = true;
// Use replace() to get all matches, and deal with Chrome/Webkit // Use replace() to get all matches, and deal with Chrome/Webkit
// overlap: // overlap:
// TODO: Do we need Mozilla next to Firefox? Other than the // TODO: Do we need Mozilla next to Firefox? Other than the
// different treatment of the Chrome/Webkit overlap // different treatment of the Chrome/Webkit overlap
// here: { chrome: true, webkit: false }, Mozilla missing is the // here: { chrome: true, webkit: false }, Mozilla missing is the
// only difference to jQuery.browser // only difference to jQuery.browser
agent.replace( user.replace(
/(opera|chrome|safari|webkit|firefox|msie|trident|atom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:([.\d]+))?/g, /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g,
function(all, n, v1, v2, rv) { function(all, n, v1, v2, rv) {
// Do not set additional browsers once chrome is detected. // Do not set additional browsers once chrome is detected.
if (!browser.chrome) { if (!agent.chrome) {
var v = n === 'opera' ? v2 : v1; var v = n === 'opera' ? v2 :
if (n === 'trident') { /^(node|trident)$/.test(n) ? rv : v1;
// Use rv: and rename to msie agent.version = v;
v = rv; agent.versionNumber = parseFloat(v);
n = 'msie'; n = n === 'trident' ? 'msie' : n;
} agent.name = n;
browser.version = v; agent[n] = true;
browser.versionNumber = parseFloat(v);
browser.name = n;
browser[n] = true;
} }
} }
); );
if (browser.chrome) if (agent.chrome)
delete browser.webkit; delete agent.webkit;
if (browser.atom) if (agent.atom)
delete browser.chrome; delete agent.chrome;
} }
/*#*/ } // __options.environment == 'browser'
}, },
/** /**
@ -261,6 +253,18 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
return this; return this;
}, },
createCanvas: function(width, height, type) {
if (type) {
// TODO: Support 'pdf' on node.js!
}
return CanvasProvider.getCanvas(width, height);
},
/**
* @deprecated, use use {@link #createCanvas(width, height)} instead.
*/
Canvas: '#createCanvas',
/** /**
* Activates this PaperScope, so all newly created items will be placed * Activates this PaperScope, so all newly created items will be placed
* in its active project. * in its active project.

View file

@ -102,13 +102,14 @@ Base.exports.PaperScript = (function() {
* @function * @function
* *
* @option options.url {String} the url of the source, for source-map * @option options.url {String} the url of the source, for source-map
* debugging * generation
* @option options.source {String} the source to be used for the source- * @option options.source {String} the source to be used for the source-
* mapping, in case the code that's passed in has already been mingled. * mapping, in case the code that's passed in has already been mingled.
* *
* @param {String} code the PaperScript code * @param {String} code the PaperScript code
* @param {Object} [option] the compilation options * @param {Object} [option] the compilation options
* @return {String} the compiled PaperScript translated into JavaScript code * @return {Object} an object holding the compiled PaperScript translated
* into JavaScript code along with source-maps and other information.
*/ */
function compile(code, options) { function compile(code, options) {
if (!code) if (!code)
@ -261,20 +262,46 @@ Base.exports.PaperScript = (function() {
break; break;
} }
} }
/*#*/ if (__options.environment == 'browser') {
// Source-map support: // Source-map support:
// Encodes a Variable Length Quantity as a Base64 string.
// See: http://www.html5rocks.com/en/tutorials/developertools/sourcemaps
function encodeVLQ(value) {
var res = '',
base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
value = (Math.abs(value) << 1) + (value < 0 ? 1 : 0);
while (value || !res) {
var next = value & (32 - 1);
value >>= 5;
if (value)
next |= 32;
res += base64[next];
}
return res;
}
var url = options.url || '', var url = options.url || '',
browser = paper.browser, agent = paper.agent,
version = browser.versionNumber, version = agent.versionNumber,
sourceMap = null, offsetCode = false,
sourceMaps = options.sourceMaps,
// Include the original code in the sourceMap if there is no linked
// source file so the debugger can still display it correctly.
source = options.source || code,
lineBreaks = /\r\n|\n|\r/mg, lineBreaks = /\r\n|\n|\r/mg,
offset = 0; offset = options.offset || 0,
map;
// TODO: Verify these browser versions for source map support, and check // TODO: Verify these browser versions for source map support, and check
// other browsers. // other browsers.
if (browser.chrome && version >= 30 if (sourceMaps && (agent.chrome && version >= 30
|| browser.webkit && version >= 537.76 // >= Safari 7.0.4 || agent.webkit && version >= 537.76 // >= Safari 7.0.4
|| browser.firefox && version >= 23) { || agent.firefox && version >= 23
if (url && window.location.href.indexOf(url) === 0) { || agent.node)) {
if (agent.node) {
// -2 required to remove function header:
// https://code.google.com/p/chromium/issues/detail?id=331655
offset -= 2;
} else if (url && window.location.href.indexOf(url) === 0) {
// If the code stems from the actual html page, determine the // If the code stems from the actual html page, determine the
// offset of inlined code. // offset of inlined code.
var html = document.getElementsByTagName('html')[0].innerHTML; var html = document.getElementsByTagName('html')[0].innerHTML;
@ -283,17 +310,21 @@ Base.exports.PaperScript = (function() {
offset = html.substr(0, html.indexOf(code) + 1).match( offset = html.substr(0, html.indexOf(code) + 1).match(
lineBreaks).length + 1; lineBreaks).length + 1;
} }
// A hack required by all current browser versions: Instead of // A hack required by older versions of browsers to align inlined
// starting the mappings at the given offset, we have to shift the // code: Instead of starting the mappings at the given offset, we
// actual code down to the place in the original file, as source-map // have to shift the actual code down to the place in the original
// support seems incomplete in these browsers. This has some // file, as source-map support seems incomplete in these browsers.
// advantages too: No code for VLQ encoding is required. offsetCode = offset > 0 && !(
// TODO: Report as bugs? agent.chrome && version >= 36 ||
var mappings = ['AAAA']; agent.safari && version >= 600 ||
// Create empty entries by the amount of lines + 1, so join can be agent.firefox && version >= 40 ||
agent.node);
var mappings = ['AA' + encodeVLQ(offsetCode ? 0 : offset) + 'A'];
// Create empty entries by the amount of lines + 1, so join can be
// used below to produce the actual instructions that many times. // used below to produce the actual instructions that many times.
mappings.length = (code.match(lineBreaks) || []).length + 1 + offset; mappings.length = (code.match(lineBreaks) || []).length + 1
sourceMap = { + (offsetCode ? offset : 0);
map = {
version: 3, version: 3,
file: url, file: url,
names:[], names:[],
@ -301,32 +332,34 @@ Base.exports.PaperScript = (function() {
// the lines of the original code, all that is required is a // the lines of the original code, all that is required is a
// mappings string that increments by one between each line. // mappings string that increments by one between each line.
// AACA is the instruction to increment the line by one. // AACA is the instruction to increment the line by one.
// TODO: Add support for column offsets!
mappings: mappings.join(';AACA'), mappings: mappings.join(';AACA'),
sourceRoot: '', sourceRoot: '',
sources: [url] sources: [url],
sourcesContent: [source]
}; };
// Include the original code in the sourceMap if there is no linked
// source file so the debugger can still display it correctly.
var source = options.source || !url && code;
if (source)
sourceMap.sourcesContent = [source];
} }
// Now do the parsing magic // Now do the parsing magic
walkAST(parse(code, { ranges: true })); walkAST(parse(code, { ranges: true }));
if (sourceMap) { if (map) {
// Adjust the line offset of the resulting code if required. if (offsetCode) {
// This is part of a browser hack, see above. // Adjust the line offset of the resulting code if required.
code = new Array(offset + 1).join('\n') + code // This is part of a browser hack, see above.
+ "\n//# sourceMappingURL=data:application/json;base64," code = new Array(offset + 1).join('\n') + code;
+ (btoa(unescape(encodeURIComponent( }
JSON.stringify(sourceMap))))) if (/^(inline|both)$/.test(sourceMaps)) {
+ "\n//# sourceURL=" + (url || 'paperscript'); code += "\n//# sourceMappingURL=data:application/json;base64,"
+ window.btoa(unescape(encodeURIComponent(
JSON.stringify(map))));
}
code += "\n//# sourceURL=" + (url || 'paperscript');
} }
/*#*/ } else { // __options.environment != 'browser' return {
// Now do the parsing magic url: url,
walkAST(parse(code, { ranges: true })); source: source,
/*#*/ } // __options.environment != 'browser' code: code,
return code; map: map
};
} }
/** /**
@ -340,14 +373,15 @@ Base.exports.PaperScript = (function() {
* @function * @function
* *
* @option options.url {String} the url of the source, for source-map * @option options.url {String} the url of the source, for source-map
* debugging * generation
* @option options.source {String} the source to be used for the source- * @option options.source {String} the source to be used for the source-
* mapping, in case the code that's passed in has already been mingled. * mapping, in case the code that's passed in has already been mingled.
* *
* @param {String} code the PaperScript code * @param {String} code the PaperScript code
* @param {PaperScope} scope the scope for which the code is executed * @param {PaperScope} scope the scope for which the code is executed
* @param {Object} [option] the compilation options * @param {Object} [option] the compilation options
* @return {String} the compiled PaperScript translated into JavaScript code * @return {Object} an object holding the compiled PaperScript translated
* into JavaScript code along with source-maps and other information.
*/ */
function execute(code, scope, options) { function execute(code, scope, options) {
// Set currently active scope. // Set currently active scope.
@ -371,8 +405,9 @@ Base.exports.PaperScript = (function() {
// function call. // function call.
params = [], params = [],
args = [], args = [],
func; func,
code = compile(code, options); compiled = typeof code === 'object' ? code : compile(code, options);
code = compiled.code;
function expose(scope, hidden) { function expose(scope, hidden) {
// Look through all enumerable properties on the scope and expose // Look through all enumerable properties on the scope and expose
// these too as pseudo-globals, but only if they seem to be in use. // these too as pseudo-globals, but only if they seem to be in use.
@ -405,20 +440,19 @@ Base.exports.PaperScript = (function() {
// We need an additional line that returns the handlers in one object. // We need an additional line that returns the handlers in one object.
if (handlers) if (handlers)
code += '\nreturn { ' + handlers + ' };'; code += '\nreturn { ' + handlers + ' };';
/*#*/ if (__options.environment == 'browser') { var agent = paper.agent;
var browser = paper.browser; if (agent.chrome || agent.firefox && agent.versionNumber < 40) {
if (browser.chrome || browser.firefox) { // On older Firefox, all error numbers inside dynamically compiled
// On Firefox, all error numbers inside dynamically compiled code // code are relative to the line where the eval / compilation
// are relative to the line where the eval / compilation happened. // happened. To fix this issue, we're temporarily inserting a new
// To fix this issue, we're temporarily inserting a new script // script tag.
// tag. We also use this on Chrome to fix an issue with compiled // We also use this on Chrome to fix issues with compiled functions:
// functions:
// https://code.google.com/p/chromium/issues/detail?id=331655 // https://code.google.com/p/chromium/issues/detail?id=331655
var script = document.createElement('script'), var script = document.createElement('script'),
head = document.head || document.getElementsByTagName('head')[0]; head = document.head || document.getElementsByTagName('head')[0];
// Add a new-line before the code on Firefox since the error // Add a new-line before the code on Firefox since the error
// messages appear to be aligned to line number 0... // messages appear to be aligned to line number 0...
if (browser.firefox) if (agent.firefox)
code = '\n' + code; code = '\n' + code;
script.appendChild(document.createTextNode( script.appendChild(document.createTextNode(
'paper._execute = function(' + params + ') {' + code + '\n}' 'paper._execute = function(' + params + ') {' + code + '\n}'
@ -430,9 +464,6 @@ Base.exports.PaperScript = (function() {
} else { } else {
func = Function(params, code); func = Function(params, code);
} }
/*#*/ } else { // __options.environment != 'browser'
func = Function(params, code);
/*#*/ } // __options.environment != 'browser'
var res = func.apply(scope, args) || {}; var res = func.apply(scope, args) || {};
// Now install the 'global' tool and view handlers, and we're done! // Now install the 'global' tool and view handlers, and we're done!
Base.each(toolHandlers, function(key) { Base.each(toolHandlers, function(key) {
@ -454,10 +485,9 @@ Base.exports.PaperScript = (function() {
// Automatically update view at the end. // Automatically update view at the end.
view.update(); view.update();
} }
return compiled;
} }
/*#*/ if (__options.environment == 'browser') {
function loadScript(script) { function loadScript(script) {
// Only load this script if it not loaded already. // Only load this script if it not loaded already.
// Support both text/paperscript and text/x-paperscript: // Support both text/paperscript and text/x-paperscript:
@ -548,41 +578,6 @@ Base.exports.PaperScript = (function() {
load: load, load: load,
parse: parse parse: parse
}; };
/*#*/ } else { // __options.environment != 'browser'
/*#*/ if (__options.environment == 'node') {
// Register the .pjs extension for automatic compilation as PaperScript
var fs = require('fs'),
path = require('path');
require.extensions['.pjs'] = function(module, uri) {
// 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 {
compile: compile,
execute: execute,
parse: parse
};
/*#*/ } // __options.environment != 'browser'
// Pass on `this` as the binding object, so we can reference Acorn both in // Pass on `this` as the binding object, so we can reference Acorn both in
// development and in the built library. // development and in the built library.
}).call(this); }).call(this);

View file

@ -22,6 +22,7 @@ var Event = Base.extend(/** @lends Event# */{
initialize: function Event(event) { initialize: function Event(event) {
this.event = event; this.event = event;
this.type = event.type;
}, },
prevented: false, prevented: false,

View file

@ -67,8 +67,8 @@ var Key = new function() {
// based on whichever key is used for commands. // based on whichever key is used for commands.
command: { command: {
get: function() { get: function() {
var browser = paper.browser; var agent = paper.agent;
return browser && browser.mac ? this.meta : this.control; return agent && agent.mac ? this.meta : this.control;
} }
} }
}); });
@ -108,8 +108,8 @@ var Key = new function() {
// Detect modifiers and mark them as pressed / released // Detect modifiers and mark them as pressed / released
if (key.length > 1 && (name = Base.camelize(key)) in modifiers) { if (key.length > 1 && (name = Base.camelize(key)) in modifiers) {
modifiers[name] = down; modifiers[name] = down;
var browser = paper.browser; var agent = paper.agent;
if (name === 'meta' && browser && browser.mac) { if (name === 'meta' && agent && agent.mac) {
// Fix a strange behavior on Mac where no keyup events are // Fix a strange behavior on Mac where no keyup events are
// received for any keys pressed while the meta key is down. // received for any keys pressed while the meta key is down.
// Keep track of the normal keys being pressed and trigger keyup // Keep track of the normal keys being pressed and trigger keyup
@ -142,14 +142,14 @@ var Key = new function() {
DomEvent.add(document, { DomEvent.add(document, {
keydown: function(event) { keydown: function(event) {
var key = getKey(event), var key = getKey(event),
browser = paper.browser; agent = paper.agent;
// Directly handle any special keys (key.length > 1) in keydown, as // Directly handle any special keys (key.length > 1) in keydown, as
// not all of them will receive keypress events. // not all of them will receive keypress events.
// Chrome doesn't fire keypress events for command and alt keys, // Chrome doesn't fire keypress events for command and alt keys,
// so we need to handle this in a way that works across all OSes. // so we need to handle this in a way that works across all OSes.
if (key.length > 1 || browser && (browser.chrome && (event.altKey if (key.length > 1 || agent && (agent.chrome && (event.altKey
|| browser.mac && event.metaKey || agent.mac && event.metaKey
|| !browser.mac && event.ctrlKey))) { || !agent.mac && event.ctrlKey))) {
handleKey(true, key, handleKey(true, key,
charLookup[key] || (key.length > 1 ? '' : key), event); charLookup[key] || (key.length > 1 ? '' : key), event);
} else { } else {

View file

@ -13,8 +13,6 @@
// First add Base and a couple of other objects that are not automatically // First add Base and a couple of other objects that are not automatically
// exported to exports (Numerical, Key, etc), then inject all exports into // exported to exports (Numerical, Key, etc), then inject all exports into
// PaperScope, and create the initial paper object, all in one statement: // PaperScope, and create the initial paper object, all in one statement:
/*#*/ if (__options.environment == 'browser') {
// NOTE: Do not create local variable `var paper` since it would shield the // NOTE: Do not create local variable `var paper` since it would shield the
// global one in the whole scope. // global one in the whole scope.
@ -23,9 +21,18 @@ paper = new (PaperScope.inject(Base.exports, {
enumerable: true, enumerable: true,
Base: Base, Base: Base,
Numerical: Numerical, Numerical: Numerical,
Key: Key Key: Key,
// Export jsdom document and window too, for Node.js
document: document,
window: window
}))(); }))();
// If we're on node, require some additional functionality now before finishing:
// - PaperScript support in require() with sourceMaps
// - exportFrames / exportImage on CanvasView
if (paper.agent.node)
require('./node/extend')(paper);
// https://github.com/umdjs/umd // https://github.com/umdjs/umd
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
// Support AMD (e.g. require.js) // Support AMD (e.g. require.js)
@ -39,21 +46,3 @@ if (typeof define === 'function' && define.amd) {
// the Base constructor function after straps.js is included. // the Base constructor function after straps.js is included.
module.exports = paper; module.exports = paper;
} }
/*#*/ } else if (__options.environment == 'node') {
paper = new (PaperScope.inject(Base.exports, {
// Mark fields as enumerable so PaperScope.inject can pick them up
enumerable: true,
Base: Base,
Numerical: Numerical,
// Export dom/node.js stuff too
XMLSerializer: XMLSerializer,
DOMParser: DOMParser,
Canvas: Canvas
}))();
// Export the paper scope.
module.exports = paper;
/*#*/ } // __options.environment == 'node'

21
src/init.js Normal file
View file

@ -0,0 +1,21 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
// Here we only make sure that there's a window and document object in the node
// environment. We can't do this directly in src/paper.js, due to the nature of
// how Prepro.js loads the include() files in the various scenarios. E.g. on
// Node.js,only the files included in such a way see each other's variables in
// their shared scope.
/* global document:true, window:true */
window = window || require('./node/window');
var document = window.document;

View file

@ -145,7 +145,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
}, },
// Only for external sources, e.g. Raster // Only for external sources, e.g. Raster
onLoad: {} onLoad: {},
onError: {}
} }
), ),

View file

@ -221,6 +221,33 @@ var Raster = Item.extend(/** @lends Raster# */{
}, },
setImage: function(image) { setImage: function(image) {
var that = this,
loaded = false;
function emit(event) {
var view = that.getView(),
type = event && event.type || 'load';
if (view && that.responds(type)) {
paper = view._scope;
that.emit(type, new Event(event));
// TODO: Request a redraw in the next animation frame from
// update() instead!
view.update();
}
}
function update() {
// Both canvas and image have width / height attributes. Due to IE,
// naturalWidth / Height needs to be checked for a swell, because it
// apparently can have width / height set to 0 when the image is
// invisible in the document.
that._size = new Size(
image ? image.naturalWidth || image.width : 0,
image ? image.naturalHeight || image.height : 0);
that._context = null;
that._changed(/*#=*/(Change.GEOMETRY | Change.PIXELS));
}
if (this._canvas) if (this._canvas)
CanvasProvider.release(this._canvas); CanvasProvider.release(this._canvas);
// Due to similarities, we can handle both canvas and image types here. // Due to similarities, we can handle both canvas and image types here.
@ -228,22 +255,30 @@ var Raster = Item.extend(/** @lends Raster# */{
// A canvas object // A canvas object
this._image = null; this._image = null;
this._canvas = image; this._canvas = image;
this._loaded = true; loaded = true;
} else { } else {
// A image object // A image object
this._image = image; this._image = image;
this._canvas = null; this._canvas = null;
this._loaded = image && image.complete; loaded = image && image.complete;
}
this._loaded = loaded;
if (loaded) {
// Emit load event with a delay, so behavior is the same as when
// it's actually loaded and we give the code time to install event.
update();
setTimeout(emit, 0);
} else if (image) {
// Trigger the load event on the image once it's loaded
DomEvent.add(image, {
load: function(event) {
that._loaded = true;
update();
emit(event);
},
error: emit
});
} }
// Both canvas and image have width / height attributes. Due to IE,
// naturalWidth / Height needs to be checked for a swell, because it
// apparently can have width / height set to 0 when the image is
// invisible in the document.
this._size = new Size(
image ? image.naturalWidth || image.width : 0,
image ? image.naturalHeight || image.height : 0);
this._context = null;
this._changed(/*#=*/(Change.GEOMETRY | Change.PIXELS));
}, },
/** /**
@ -324,95 +359,19 @@ var Raster = Item.extend(/** @lends Raster# */{
*/ */
getSource: function() { getSource: function() {
var image = this._image; var image = this._image;
/*#*/ if (__options.environment == 'node') {
// See #toDataURL()
image = image && this._src;
/*#*/ } // __options.environment == 'node'
return image && image.src || this.toDataURL(); return image && image.src || this.toDataURL();
}, },
setSource: function(src) { setSource: function(src) {
var that = this, var crossOrigin = this._crossOrigin,
crossOrigin = this._crossOrigin, // src can be an URL or a DOM ID to load the image from:
image; image = document.getElementById(src) || new window.Image();
function loaded() {
var view = that.getView();
if (view) {
paper = view._scope;
that.setImage(image);
that.emit('load');
view.update();
}
}
/*#*/ if (__options.environment == 'browser') {
// src can be an URL or a DOM ID to load the image from
image = document.getElementById(src) || new Image();
if (crossOrigin) if (crossOrigin)
image.crossOrigin = crossOrigin; image.crossOrigin = crossOrigin;
// IE has naturalWidth / Height defined, but width / height set to 0 // A new image created above? Set the source now.
// when the image is invisible in the document. if (!image.src)
if (image.naturalWidth && image.naturalHeight) {
// Emit load event with a delay, so behavior is the same as when
// it's actually loaded and we give the code time to install event.
setTimeout(loaded, 0);
} else {
// Trigger the load event on the image once it's loaded
DomEvent.add(image, { load: loaded });
// A new image created above? Set the source now.
if (!image.src)
image.src = src;
}
this.setImage(image);
/*#*/ } else if (__options.environment == 'node') {
image = new Image();
if (crossOrigin)
image.crossOrigin = crossOrigin;
// If we're running on the server and it's a string,
// check if it is a data URL
if (/^data:/.test(src)) {
// Preserve the data in this._src since canvas-node eats it.
image.src = src; image.src = src;
this._src = { src: src, data: src };
// Emit load event with a delay, so behavior is the same as when
// it's actually loaded and we give the code time to install event.
setTimeout(loaded, 0);
} else if (/^https?:\/\//.test(src)) {
// Load it from remote location:
require('request').get({
url: src,
encoding: null // So the response data is a Buffer
}, function (err, response, buffer) {
if (err)
throw err;
if (response.statusCode == 200) {
image.src = buffer;
that._src = {
src: src,
buffer: buffer,
type: response.headers['content-type']
};
loaded();
}
});
} else {
// Load it from disk:
src = (src.match(/^file:\/\/(.*)$/) || [null, src])[1];
require('fs').readFile(src, function (err, buffer) {
if (err)
throw err;
image.src = buffer;
that._src = {
src: 'file://' + src,
buffer: buffer,
type: require('mime').lookup(src)
};
loaded();
});
}
this.setImage(image); this.setImage(image);
/*#*/ } // __options.environment == 'node'
}, },
/** /**
@ -500,21 +459,7 @@ var Raster = Item.extend(/** @lends Raster# */{
// See if the linked image is base64 encoded already, if so reuse it, // See if the linked image is base64 encoded already, if so reuse it,
// otherwise try using canvas.toDataURL() // otherwise try using canvas.toDataURL()
var image = this._image, var image = this._image,
src; src = image && image.src;
/*#*/ if (__options.environment == 'node') {
// Only use the information stored in _src if we still use the _image
// that goes with it.
var obj = image && this._src;
if (obj) {
if (!obj.data) {
obj.data = 'data:' + obj.type + ';base64,' +
obj.buffer.toString('base64');
}
src = obj.data;
}
/*#*/ } else {
src = image && image.src;
/*#*/ } // __options.environment == 'node'
if (/^data:/.test(src)) if (/^data:/.test(src))
return src; return src;
var canvas = this.getCanvas(); var canvas = this.getCanvas();

View file

@ -14,7 +14,7 @@
// the browser, avoiding the step of having to manually preprocess it after each // the browser, avoiding the step of having to manually preprocess it after each
// change. This is very useful during development of the library itself. // change. This is very useful during development of the library itself.
if (typeof window === 'object') { if (typeof window === 'object') {
// Browser based loading through Prepro.js: // Browser based loading through Prepro.js:
/* jshint -W082 */ /* jshint -W082 */
function load(src) { function load(src) {
@ -28,9 +28,9 @@ if (typeof window === 'object') {
src = scripts[scripts.length - 1].getAttribute('src'); src = scripts[scripts.length - 1].getAttribute('src');
// Assume that we're loading from a non-root folder, either through // Assume that we're loading from a non-root folder, either through
// ../../dist/paper-full.js, or directly through ../../src/load.js, // ../../dist/paper-full.js, or directly through ../../src/load.js,
// and match root as all the parts of the path that lead to that folder. // and match root as all the parts of the path that lead to that folder,
// So we basically just want all the leading '.' and '/' characters: // exclude the last bit (dist|src), since that's the sub-folder of paper
var root = src.match(/^([.\/]*)/)[1]; var root = src.match(/^(.*\/)\w*\//)[1];
// First load the prepro's browser.js file, which provides the include() // First load the prepro's browser.js file, which provides the include()
// function for the browser. // function for the browser.
load(root + 'node_modules/prepro/lib/browser.js'); load(root + 'node_modules/prepro/lib/browser.js');
@ -40,23 +40,31 @@ if (typeof window === 'object') {
load(root + 'src/load.js'); load(root + 'src/load.js');
} else { } else {
include('options.js'); include('options.js');
// Load constants.js, required by the on-the-fly preprocessing:
include('constants.js');
// Automatically load stats.js while developing.
include('../node_modules/stats.js/build/stats.min.js');
include('paper.js'); include('paper.js');
} }
} else { } else {
// Node.js based loading through Prepro.js: // Node.js based loading through Prepro.js:
var prepro = require('prepro/lib/node.js'), var prepro = require('prepro/lib/node.js'),
// Load the default browser-based options for further amendments. // Load the default browser-based options for further amendments.
// Step out and back into src, if this is loaded from dist/paper-node.js // Step out and back into src, in case this is loaded from
// dist/paper-node.js
options = require('../src/options.js'); options = require('../src/options.js');
// Override Node.js specific options. // Override Node.js specific options.
options.version += '-load'; options.version += '-load';
options.environment = 'node';
options.load = true; options.load = true;
prepro.setup(function() { prepro.setup(function() {
// Return objects to be defined in the preprocess-scope. // Return objects to be defined in the preprocess-scope.
// Note that this would be merge in with already existing objects. // Note that this would be merge in with already existing objects.
return { __options: options }; // We're defining window here since the paper-scope argument is only
// available in the included scripts when the library is actually built.
return { __options: options, window: null };
}); });
// Load constants.js, required by the on-the-fly preprocessing:
prepro.include('../src/constants.js');
// Load Paper.js library files. // Load Paper.js library files.
prepro.include('../src/paper.js'); prepro.include('../src/paper.js');
} }

View file

@ -14,7 +14,7 @@ var Http = {
request: function(method, url, callback, async) { request: function(method, url, callback, async) {
// Code borrowed from Coffee Script and extended: // Code borrowed from Coffee Script and extended:
async = (async === undefined) ? true : async; async = (async === undefined) ? true : async;
var ctor = window.ActiveXObject || XMLHttpRequest, var ctor = window.ActiveXObject || window.XMLHttpRequest,
xhr = new ctor('Microsoft.XMLHTTP'); xhr = new ctor('Microsoft.XMLHTTP');
xhr.open(method.toUpperCase(), url, async); xhr.open(method.toUpperCase(), url, async);
if ('overrideMimeType' in xhr) if ('overrideMimeType' in xhr)

125
src/node/extend.js Normal file
View file

@ -0,0 +1,125 @@
/*
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
* http://paperjs.org/
*
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
* http://scratchdisk.com/ & http://jonathanpuckey.com/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* All rights reserved.
*/
var fs = require('fs'),
path = require('path');
module.exports = function(paper) {
var sourceMaps = {},
sourceMapSupprt = 'require("source-map-support").install(paper.PaperScript.sourceMapSupport);\n';
paper.PaperScript.sourceMapSupport = {
retrieveSourceMap: function(source) {
var map = sourceMaps[source];
return map ? { url: source, map: map } : null;
}
};
// Register the .pjs extension for automatic compilation as PaperScript
require.extensions['.pjs'] = function(module, filename) {
// 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 = fs.readFileSync(filename, 'utf8'),
code = sourceMapSupprt + source,
compiled = paper.PaperScript.compile(code, {
url: filename,
source: source,
sourceMaps: true,
offset: -1 // remove sourceMapSupprt...
}),
scope = new paper.PaperScope();
// Keep track of sourceMaps so retrieveSourceMap() can link them up
scope.setup(canvas);
scope.__filename = filename;
scope.__dirname = path.dirname(filename);
// Expose core methods and values
scope.require = require;
scope.console = console;
sourceMaps[filename] = compiled.map;
paper.PaperScript.execute(compiled, scope);
return scope;
};
};
// Node.js based image exporting code.
paper.CanvasView.inject({
// DOCS: CanvasView#exportFrames(param);
exportFrames: function(param) {
param = new paper.Base({
fps: 30,
prefix: 'frame-',
amount: 1
}, param);
if (!param.directory) {
throw new Error('Missing param.directory');
}
var view = this,
count = 0,
frameDuration = 1 / param.fps,
startTime = Date.now(),
lastTime = startTime;
// Start exporting frames by exporting the first frame:
exportFrame(param);
function exportFrame(param) {
var file = path.join(param.directory,
param.prefix + ('000000' + count).slice(-6) + '.png');
var out = view.exportImage(file, function() {
// When the file has been closed, export the next fame:
var then = Date.now();
if (param.onProgress) {
param.onProgress({
count: count,
amount: param.amount,
percentage: Math.round(count / param.amount
* 10000) / 100,
time: then - startTime,
delta: then - lastTime
});
}
lastTime = then;
if (count < param.amount) {
exportFrame(param);
} else {
// Call onComplete handler when finished:
if (param.onComplete) {
param.onComplete();
}
}
});
// Convert to a Base object, for #toString()
view.emit('frame', new paper.Base({
delta: frameDuration,
time: frameDuration * count,
count: count
}));
count++;
}
},
// DOCS: CanvasView#exportImage(path, callback);
exportImage: function(path, callback) {
this.draw();
var out = fs.createWriteStream(path),
stream = this._element.createPNGStream();
// Pipe the png stream to the write stream:
stream.pipe(out);
if (callback) {
out.on('close', callback);
}
return out;
}
});
};

View file

@ -10,22 +10,30 @@
* All rights reserved. * All rights reserved.
*/ */
// Node.js emulation layer of browser based environment, based on node-canvas // Node.js emulation layer of browser environment, based on jsdom with node-
// and jsdom. // canvas integration.
/* global document:true, window:true, navigator:true, HTMLCanvasElement:true,
Image:true */
var jsdom = require('jsdom'), var jsdom = require('jsdom'),
// Node Canvas library: https://github.com/learnboost/node-canvas idlUtils = require('jsdom/lib/jsdom/living/generated/utils');
Canvas = require('canvas'),
// Expose global browser variables and create a document and a window using // Create our document and window objects through jsdom.
// jsdom, e.g. for import/exportSVG() /* global document:true, window:true */
document = jsdom.jsdom('<html><body></body></html>'), var document = jsdom.jsdom('<html><body></body></html>', {
window = document.defaultView, features: {
navigator = window.navigator, FetchExternalResources : ['img', 'script']
HTMLCanvasElement = Canvas, }
Image = Canvas.Image; }),
window = document.defaultView;
['pngStream', 'createPNGStream', 'jpgStream', 'createJPGStream'].forEach(
function(key) {
this[key] = function() {
var impl = this._canvas ? this : idlUtils.implForWrapper(this),
canvas = impl && impl._canvas;
return canvas[key].apply(canvas, arguments);
};
},
window.HTMLCanvasElement.prototype);
// Define XMLSerializer and DOMParser shims, to emulate browser behavior. // Define XMLSerializer and DOMParser shims, to emulate browser behavior.
// TODO: Put this into a simple node module, with dependency on jsdom? // TODO: Put this into a simple node module, with dependency on jsdom?
@ -56,3 +64,8 @@ DOMParser.prototype.parseFromString = function(string, contenType) {
div.innerHTML = string; div.innerHTML = string;
return div.firstChild; return div.firstChild;
}; };
window.XMLSerializer = XMLSerializer;
window.DOMParser = DOMParser;
module.exports = window;

View file

@ -23,7 +23,6 @@ var load = typeof window === 'object';
var __options = { var __options = {
version: version + (load ? '-load' : ''), version: version + (load ? '-load' : ''),
environment: 'browser',
load: load, load: load,
parser: 'acorn', parser: 'acorn',
svg: true, svg: true,

View file

@ -32,17 +32,10 @@
// Allow the minification of the undefined variable by defining it as a local // Allow the minification of the undefined variable by defining it as a local
// parameter inside the paper scope. // parameter inside the paper scope.
var paper = new function(undefined) { var paper = function(window, undefined) {
/*#*/ include('init.js');
// Inline Straps.js core (the Base class) inside the paper scope first: // Inline Straps.js core (the Base class) inside the paper scope first:
/*#*/ include('../node_modules/straps/straps.js', { exports: false }); /*#*/ include('../node_modules/straps/straps.js');
/*#*/ if (__options.load && __options.environment == 'browser') {
/*#*/ include('../node_modules/stats.js/build/stats.min.js');
/*#*/ }
/*#*/ if (__options.load) {
/*#*/ include('constants.js');
/*#*/ }
/*#*/ include('core/Base.js'); /*#*/ include('core/Base.js');
/*#*/ include('core/Emitter.js'); /*#*/ include('core/Emitter.js');
@ -96,29 +89,21 @@ var paper = new function(undefined) {
/*#*/ include('style/GradientStop.js'); /*#*/ include('style/GradientStop.js');
/*#*/ include('style/Style.js'); /*#*/ include('style/Style.js');
/*#*/ if (__options.environment == 'node') {
/*#*/ include('dom/node.js');
/*#*/ }
/*#*/ include('dom/DomElement.js'); /*#*/ include('dom/DomElement.js');
/*#*/ if (__options.environment == 'browser') { /*#*/ include('dom/DomEvent.js');
// DomEvent doesn't make sense outside of the browser (yet)
/*#*/ include('dom/DomEvent.js');
/*#*/ }
/*#*/ include('view/View.js'); /*#*/ include('view/View.js');
/*#*/ include('view/CanvasView.js'); /*#*/ include('view/CanvasView.js');
/*#*/ if (__options.environment == 'browser') { /*#*/ include('event/Event.js');
/*#*/ include('event/Event.js'); /*#*/ include('event/KeyEvent.js');
/*#*/ include('event/KeyEvent.js'); /*#*/ include('event/Key.js');
/*#*/ include('event/Key.js'); /*#*/ include('event/MouseEvent.js');
/*#*/ include('event/MouseEvent.js');
/*#*/ include('tool/ToolEvent.js'); /*#*/ include('tool/ToolEvent.js');
/*#*/ include('tool/Tool.js'); /*#*/ include('tool/Tool.js');
/*#*/ include('net/Http.js'); /*#*/ include('net/Http.js');
/*#*/ }
/*#*/ include('canvas/CanvasProvider.js'); /*#*/ include('canvas/CanvasProvider.js');
/*#*/ include('canvas/BlendMode.js'); /*#*/ include('canvas/BlendMode.js');
@ -139,4 +124,4 @@ var paper = new function(undefined) {
/*#*/ include('export.js'); /*#*/ include('export.js');
return paper; return paper;
}; }(this.window);

View file

@ -380,7 +380,7 @@ new function() {
definitions = null; definitions = null;
} }
return options.asString return options.asString
? new XMLSerializer().serializeToString(svg) ? new window.XMLSerializer().serializeToString(svg)
: svg; : svg;
} }

View file

@ -566,7 +566,6 @@ new function() {
// as this is how SVG works too. // as this is how SVG works too.
// See if it's a string but handle markup separately // See if it's a string but handle markup separately
if (typeof source === 'string' && !/^.*</.test(source)) { if (typeof source === 'string' && !/^.*</.test(source)) {
/*#*/ if (__options.environment == 'browser') {
// First see if we're meant to import an element with the given // First see if we're meant to import an element with the given
// id. // id.
node = document.getElementById(source); node = document.getElementById(source);
@ -577,9 +576,6 @@ new function() {
} else { } else {
return Http.request('get', source, onLoadCallback); return Http.request('get', source, onLoadCallback);
} }
/*#*/ } else if (__options.environment == 'node') {
// TODO: Implement!
/*#*/ } // __options.environment == 'node'
} else if (typeof File !== 'undefined' && source instanceof File) { } else if (typeof File !== 'undefined' && source instanceof File) {
// Load local file through FileReader // Load local file through FileReader
var reader = new FileReader(); var reader = new FileReader();
@ -591,7 +587,8 @@ new function() {
} }
if (typeof source === 'string') if (typeof source === 'string')
node = new DOMParser().parseFromString(source, 'image/svg+xml'); node = new window.DOMParser().parseFromString(source,
'image/svg+xml');
if (!node.nodeName) if (!node.nodeName)
throw new Error('Unsupported SVG source: ' + source); throw new Error('Unsupported SVG source: ' + source);
// jsdom in Node.js uses uppercase values for nodeName... // jsdom in Node.js uses uppercase values for nodeName...

View file

@ -33,7 +33,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
*/ */
initialize: function CanvasView(project, canvas) { initialize: function CanvasView(project, canvas) {
// Handle canvas argument // Handle canvas argument
if (!(canvas instanceof HTMLCanvasElement)) { if (!(canvas instanceof window.HTMLCanvasElement)) {
// See if the arguments describe the view size: // See if the arguments describe the view size:
var size = Size.read(arguments, 1); var size = Size.read(arguments, 1);
if (size.isZero()) if (size.isZero())
@ -44,7 +44,6 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
} }
this._context = canvas.getContext('2d'); this._context = canvas.getContext('2d');
this._pixelRatio = 1; this._pixelRatio = 1;
/*#*/ if (__options.environment == 'browser') {
if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) { if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {
// Hi-DPI Canvas support based on: // Hi-DPI Canvas support based on:
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/ // http://www.html5rocks.com/en/tutorials/canvas/hidpi/
@ -53,19 +52,15 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
'backingStorePixelRatio') || 1; 'backingStorePixelRatio') || 1;
this._pixelRatio = deviceRatio / backingStoreRatio; this._pixelRatio = deviceRatio / backingStoreRatio;
} }
/*#*/ } // __options.environment == 'browser'
View.call(this, project, canvas); View.call(this, project, canvas);
}, },
_setViewSize: function(size) { _setViewSize: function _setViewSize(width, height) {
var element = this._element, var pixelRatio = this._pixelRatio;
pixelRatio = this._pixelRatio,
width = size.width,
height = size.height;
// Upscale the canvas if the pixel ratio is more than 1. // Upscale the canvas if the pixel ratio is more than 1.
element.width = width * pixelRatio; _setViewSize.base.call(this, width * pixelRatio, height * pixelRatio);
element.height = height * pixelRatio;
if (pixelRatio !== 1) { if (pixelRatio !== 1) {
var element = this._element;
// We need to set the correct size on non-resizable canvases through // We need to set the correct size on non-resizable canvases through
// their style when HiDPI is active, as otherwise they would appear // their style when HiDPI is active, as otherwise they would appear
// too big. // too big.
@ -85,9 +80,9 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
* pixels. * pixels.
*/ */
getPixelSize: function(size) { getPixelSize: function(size) {
var browser = paper.browser, var agent = paper.agent,
pixels; pixels;
if (browser && browser.firefox) { if (agent && agent.firefox) {
// Firefox doesn't appear to convert context.font sizes to pixels, // Firefox doesn't appear to convert context.font sizes to pixels,
// while other browsers do. Workaround: // while other browsers do. Workaround:
var parent = this._element.parentNode, var parent = this._element.parentNode,
@ -132,9 +127,6 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
var project = this._project; var project = this._project;
if (!project || !force && !project._needsUpdate) if (!project || !force && !project._needsUpdate)
return false; return false;
// Initial tests conclude that clearing the canvas using clearRect
// is always faster than setting canvas.width = canvas.width
// http://jsperf.com/clearrect-vs-setting-width/7
var ctx = this._context, var ctx = this._context,
size = this._viewSize; size = this._viewSize;
ctx.clearRect(0, 0, size.width + 1, size.height + 1); ctx.clearRect(0, 0, size.width + 1, size.height + 1);
@ -143,90 +135,3 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
return true; return true;
} }
}); });
/*#*/ if (__options.environment == 'node') {
// Node.js based image exporting code.
CanvasView.inject(new function() {
// Utility function that converts a number to a string with
// x amount of padded 0 digits:
function toPaddedString(number, length) {
var str = number.toString(10);
for (var i = 0, l = length - str.length; i < l; i++) {
str = '0' + str;
}
return str;
}
var fs = require('fs');
return {
// DOCS: CanvasView#exportFrames(param);
exportFrames: function(param) {
param = new Base({
fps: 30,
prefix: 'frame-',
amount: 1
}, param);
if (!param.directory) {
throw new Error('Missing param.directory');
}
var view = this,
count = 0,
frameDuration = 1 / param.fps,
startTime = Date.now(),
lastTime = startTime;
// Start exporting frames by exporting the first frame:
exportFrame(param);
function exportFrame(param) {
var filename = param.prefix + toPaddedString(count, 6) + '.png',
path = param.directory + '/' + filename;
var out = view.exportImage(path, function() {
// When the file has been closed, export the next fame:
var then = Date.now();
if (param.onProgress) {
param.onProgress({
count: count,
amount: param.amount,
percentage: Math.round(count / param.amount
* 10000) / 100,
time: then - startTime,
delta: then - lastTime
});
}
lastTime = then;
if (count < param.amount) {
exportFrame(param);
} else {
// Call onComplete handler when finished:
if (param.onComplete) {
param.onComplete();
}
}
});
// Use new Base() to convert into a Base object, for #toString()
view.emit('frame', new Base({
delta: frameDuration,
time: frameDuration * count,
count: count
}));
count++;
}
},
// DOCS: CanvasView#exportImage(path, callback);
exportImage: function(path, callback) {
this.draw();
var out = fs.createWriteStream(path),
stream = this._element.createPNGStream();
// Pipe the png stream to the write stream:
stream.pipe(out);
if (callback) {
out.on('close', callback);
}
return out;
}
};
});
/*#*/ } // __options.environment == 'node'

View file

@ -19,7 +19,6 @@
* center, both useful for constructing artwork that should appear centered on * center, both useful for constructing artwork that should appear centered on
* screen. * screen.
*/ */
/* jshint -W082 */// Do not complain about functions inside Prepro.js statements
var View = Base.extend(Emitter, /** @lends View# */{ var View = Base.extend(Emitter, /** @lends View# */{
_class: 'View', _class: 'View',
@ -29,8 +28,6 @@ var View = Base.extend(Emitter, /** @lends View# */{
this._project = project; this._project = project;
this._scope = project._scope; this._scope = project._scope;
this._element = element; this._element = element;
var size;
/*#*/ if (__options.environment == 'browser') {
// Sub-classes may set _pixelRatio first // Sub-classes may set _pixelRatio first
if (!this._pixelRatio) if (!this._pixelRatio)
this._pixelRatio = window.devicePixelRatio || 1; this._pixelRatio = window.devicePixelRatio || 1;
@ -80,7 +77,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
// it might have been set to a % size, in which case it would use some // it might have been set to a % size, in which case it would use some
// default internal size (300x150 on WebKit) and scale up the pixels. // default internal size (300x150 on WebKit) and scale up the pixels.
// We also need this call here for HiDPI support. // We also need this call here for HiDPI support.
this._setViewSize(size = getCanvasSize()); var size = this._viewSize = getCanvasSize();
this._setViewSize(size.width, size.height);
// TODO: Test this on IE: // TODO: Test this on IE:
if (PaperScope.hasAttribute(element, 'stats') if (PaperScope.hasAttribute(element, 'stats')
&& typeof Stats !== 'undefined') { && typeof Stats !== 'undefined') {
@ -94,19 +92,10 @@ var View = Base.extend(Emitter, /** @lends View# */{
style.top = offset.y + 'px'; style.top = offset.y + 'px';
document.body.appendChild(stats); document.body.appendChild(stats);
} }
/*#*/ } else if (__options.environment == 'node') {
// Sub-classes may set _pixelRatio first
if (!this._pixelRatio)
this._pixelRatio = 1;
// Generate an id for this view
this._id = 'view-' + View._id++;
size = new Size(element.width, element.height);
/*#*/ } // __options.environment == 'node'
// Keep track of views internally // Keep track of views internally
View._views.push(this); View._views.push(this);
// Link this id to our view // Link this id to our view
View._viewsById[this._id] = this; View._viewsById[this._id] = this;
this._viewSize = size;
(this._matrix = new Matrix())._owner = this; (this._matrix = new Matrix())._owner = this;
this._zoom = 1; this._zoom = 1;
// Make sure the first view is focused for keyboard input straight away // Make sure the first view is focused for keyboard input straight away
@ -135,11 +124,9 @@ var View = Base.extend(Emitter, /** @lends View# */{
var project = this._project; var project = this._project;
if (project._view === this) if (project._view === this)
project._view = null; project._view = null;
/*#*/ if (__options.environment == 'browser') {
// Uninstall event handlers again for this view. // Uninstall event handlers again for this view.
DomEvent.remove(this._element, this._viewEvents); DomEvent.remove(this._element, this._viewEvents);
DomEvent.remove(window, this._windowEvents); DomEvent.remove(window, this._windowEvents);
/*#*/ } // __options.environment == 'browser'
this._element = this._project = null; this._element = this._project = null;
// Remove all onFrame handlers. // Remove all onFrame handlers.
// TODO: Shouldn't we remove all handlers, automatically // TODO: Shouldn't we remove all handlers, automatically
@ -174,7 +161,6 @@ var View = Base.extend(Emitter, /** @lends View# */{
_count: 0, _count: 0,
_requestFrame: function() { _requestFrame: function() {
/*#*/ if (__options.environment == 'browser') {
var that = this; var that = this;
DomEvent.requestAnimationFrame(function() { DomEvent.requestAnimationFrame(function() {
that._requested = false; that._requested = false;
@ -186,7 +172,6 @@ var View = Base.extend(Emitter, /** @lends View# */{
that._handleFrame(); that._handleFrame();
}, this._element); }, this._element);
this._requested = true; this._requested = true;
/*#*/ } // __options.environment == 'browser'
}, },
_handleFrame: function() { _handleFrame: function() {
@ -319,11 +304,13 @@ var View = Base.extend(Emitter, /** @lends View# */{
setViewSize: function(/* size */) { setViewSize: function(/* size */) {
var size = Size.read(arguments), var size = Size.read(arguments),
width = size.width,
height = size.height,
delta = size.subtract(this._viewSize); delta = size.subtract(this._viewSize);
if (delta.isZero()) if (delta.isZero())
return; return;
this._viewSize.set(size.width, size.height); this._viewSize.set(width, height);
this._setViewSize(size); this._setViewSize(width, height);
// Call onResize handler on any size change // Call onResize handler on any size change
this.emit('resize', { this.emit('resize', {
size: size, size: size,
@ -334,12 +321,14 @@ var View = Base.extend(Emitter, /** @lends View# */{
}, },
/** /**
* Private method, overriden in CanvasView for HiDPI support. * Private method, overridden in CanvasView for HiDPI support.
*/ */
_setViewSize: function(size) { _setViewSize: function(width, height) {
var element = this._element; var element = this._element;
element.width = size.width; if (element.width !== width)
element.height = size.height; element.width = width;
if (element.height !== height)
element.height = height;
}, },
/** /**
@ -556,12 +545,10 @@ var View = Base.extend(Emitter, /** @lends View# */{
*/ */
play: function() { play: function() {
this._animate = true; this._animate = true;
/*#*/ if (__options.environment == 'browser') {
// Request a frame handler straight away to initialize the // Request a frame handler straight away to initialize the
// sequence of onFrame calls. // sequence of onFrame calls.
if (!this._requested) if (!this._requested)
this._requestFrame(); this._requestFrame();
/*#*/ } // __options.environment == 'browser'
}, },
/** /**
@ -806,10 +793,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
_id: 0, _id: 0,
create: function(project, element) { create: function(project, element) {
/*#*/ if (__options.environment == 'browser') {
if (typeof element === 'string') if (typeof element === 'string')
element = document.getElementById(element); element = document.getElementById(element);
/*#*/ } // __options.environment == 'browser'
// Factory to provide the right View subclass for a given element. // Factory to provide the right View subclass for a given element.
// Produces only CanvasViews for now: // Produces only CanvasViews for now:
return new CanvasView(project, element); return new CanvasView(project, element);
@ -817,8 +802,6 @@ var View = Base.extend(Emitter, /** @lends View# */{
} }
}, },
new function() { // Injection scope for mouse events on the browser new function() { // Injection scope for mouse events on the browser
/*#*/ if (__options.environment == 'browser') {
/** /**
* Native event handling, coordinate conversion, focus handling and * Native event handling, coordinate conversion, focus handling and
* delegation to view and tool objects. * delegation to view and tool objects.
@ -1227,5 +1210,4 @@ new function() { // Injection scope for mouse events on the browser
updateFocus: updateFocus updateFocus: updateFocus
} }
}; };
/*#*/ } // __options.environment == 'browser'
}); });

View file

@ -35,7 +35,7 @@ var errorHandler = console.error;
console.error = function() { console.error = function() {
QUnit.pushFailure([].join.call(arguments, ' '), QUnit.config.current.stack); QUnit.pushFailure([].join.call(arguments, ' '), QUnit.config.current.stack);
errorHandler.apply(this, arguments); errorHandler.apply(this, arguments);
} };
// Register a jsDump parser for Base. // Register a jsDump parser for Base.
QUnit.jsDump.setParser('Base', function (obj, stack) { QUnit.jsDump.setParser('Base', function (obj, stack) {
@ -75,14 +75,14 @@ function compareItem(actual, expected, message, options, properties) {
function getImageTag(raster) { function getImageTag(raster) {
return '<img width="' + raster.width + '" height="' + raster.height return '<img width="' + raster.width + '" height="' + raster.height
+ '" src="' + raster.source + '">' + '" src="' + raster.source + '">';
} }
if (options && options.rasterize) { if (options && options.rasterize) {
// In order to properly compare pixel by pixel, we need to put each item // In order to properly compare pixel by pixel, we need to put each item
// into a group with a white background of the united dimensions of the // into a group with a white background of the united dimensions of the
// bounds of both items before rasterizing. // bounds of both items before rasterizing.
var resolution = options.rasterize == true ? 72 : options.rasterize, var resolution = options.rasterize === true ? 72 : options.rasterize,
actualBounds = actual.strokeBounds, actualBounds = actual.strokeBounds,
expecedBounds = expected.strokeBounds, expecedBounds = expected.strokeBounds,
bounds = actualBounds.isEmpty() bounds = actualBounds.isEmpty()
@ -120,7 +120,7 @@ function compareItem(actual, expected, message, options, properties) {
.onComplete(function(data) { result = data; }); .onComplete(function(data) { result = data; });
var identical = result ? 100 - result.misMatchPercentage : 0, var identical = result ? 100 - result.misMatchPercentage : 0,
ok = identical == 100, ok = identical == 100,
text = identical.toFixed(2) + '% identical' text = identical.toFixed(2) + '% identical';
QUnit.push(ok, text, '100.00% identical', message); QUnit.push(ok, text, '100.00% identical', message);
if (!ok && result) { if (!ok && result) {
// Get the right entry for this unit test and assertion, and // Get the right entry for this unit test and assertion, and
@ -407,7 +407,7 @@ function createSVG(str, attrs) {
node.setAttribute(key, attrs[key]); node.setAttribute(key, attrs[key]);
return node; return node;
} else { } else {
return new DOMParser().parseFromString( return new window.DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg">' + str + '</svg>', '<svg xmlns="http://www.w3.org/2000/svg">' + str + '</svg>',
'text/xml'); 'text/xml');
} }