More work on unified version for browsers and node.

Relates to #739
This commit is contained in:
Jürg Lehni 2016-01-26 20:02:23 +01:00
parent 15797903cf
commit e1a51f858a
15 changed files with 249 additions and 253 deletions

1
dist/paper-node.js vendored
View file

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

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

@ -16,13 +16,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'));
}); });
gulp.task('clean:load', function() { gulp.task('clean:load', function() {
return del([ return del([ 'dist/paper-full.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"
}, },

View file

@ -72,7 +72,7 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
CanvasProvider.release(ctx); CanvasProvider.release(ctx);
} }
if (!this.agent) { if (!this.agent) {
var user = navigator.userAgent.toLowerCase(), var user = window.navigator.userAgent.toLowerCase(),
// Detect basic platforms, only mac internally required for now. // Detect basic platforms, only mac internally required for now.
os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0], os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0],
platform = os === 'darwin' ? 'mac' : os, platform = os === 'darwin' ? 'mac' : os,

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,26 +21,17 @@ 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
/*#*/ } 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,
HTMLCanvasElement: HTMLCanvasElement,
Image: Image,
document: document, document: document,
window: window window: window
}))(); }))();
/*#*/ } // __options.environment == 'node' // If we're on node, require some additional functionality now (PaperScript
// support in require() with sourceMaps, and 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) {

18
src/init.js Normal file
View file

@ -0,0 +1,18 @@
/*
* 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.
*/
// Node.js emulation layer of browser based environment, based on node-canvas
// and jsdom.
/* global document:true, window:true */
window = window || require('./node/window');
var document = window.document;

View file

@ -343,7 +343,7 @@ var Raster = Item.extend(/** @lends Raster# */{
} }
// src can be an URL or a DOM ID to load the image from // src can be an URL or a DOM ID to load the image from
image = document.getElementById(src) || new Image(); image = document.getElementById(src) || new window.Image();
if (crossOrigin) if (crossOrigin)
image.crossOrigin = crossOrigin; image.crossOrigin = crossOrigin;
if (image.complete) { if (image.complete) {

View file

@ -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

@ -1,113 +0,0 @@
/*
* 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.
*/
// Node.js emulation layer of browser based environment, based on node-canvas
// and jsdom.
/* global document:true, window:true, navigator:true, HTMLCanvasElement:true,
Image:true */
var jsdom = require('jsdom'),
idlUtils = require('jsdom/lib/jsdom/living/generated/utils'),
fs = require('fs'),
path = require('path');
// Expose global browser variables and create a document and a window using
// jsdom.
var document = jsdom.jsdom('<html><body></body></html>', {
features: {
FetchExternalResources : ['img', 'script']
}
}),
window = document.defaultView,
navigator = window.navigator,
HTMLCanvasElement = window.HTMLCanvasElement,
Image = window.Image;
Base.each(
['pngStream', 'createPNGStream', 'jpgStream', 'createJPGStream'],
function(key) {
this[key] = function() {
var impl = this._canvas ? this : idlUtils.implForWrapper(this),
canvas = impl && impl._canvas;
return canvas[key].apply(canvas, arguments);
};
},
HTMLCanvasElement.prototype);
// Define XMLSerializer and DOMParser shims, to emulate browser behavior.
// TODO: Put this into a simple node module, with dependency on jsdom?
function XMLSerializer() {
}
XMLSerializer.prototype.serializeToString = function(node) {
var text = jsdom.serializeDocument(node);
// Fix a jsdom issue where all SVG tagNames are lowercased:
// https://github.com/tmpvar/jsdom/issues/620
var tagNames = ['linearGradient', 'radialGradient', 'clipPath'];
for (var i = 0, l = tagNames.length; i < l; i++) {
var tagName = tagNames[i];
text = text.replace(
new RegExp('(<|</)' + tagName.toLowerCase() + '\\b', 'g'),
function(all, start) {
return start + tagName;
});
}
return text;
};
function DOMParser() {
}
DOMParser.prototype.parseFromString = function(string, contenType) {
var div = document.createElement('div');
div.innerHTML = string;
return div.firstChild;
};
var sourceMaps = {},
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) {
// TODO: Fix this once we can require('paper') from node specific code.
paper.PaperScript.sourceMapSupport = sourceMapSupport;
var source = fs.readFileSync(filename, 'utf8'),
code = 'require("source-map-support").install(paper.PaperScript.sourceMapSupport);\n' + source,
compiled = paper.PaperScript.compile(code, {
url: filename,
source: source,
sourceMaps: true,
offset: -1 // remove require("source-map-support")...
}),
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;
};
};

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;
}
});
};

71
src/node/window.js Normal file
View file

@ -0,0 +1,71 @@
/*
* 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.
*/
// Node.js emulation layer of browser environment, based on jsdom with node-
// canvas integration.
var jsdom = require('jsdom'),
idlUtils = require('jsdom/lib/jsdom/living/generated/utils');
// Create our document and window objects through jsdom.
/* global document:true, window:true */
var document = jsdom.jsdom('<html><body></body></html>', {
features: {
FetchExternalResources : ['img', 'script']
}
}),
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.
// TODO: Put this into a simple node module, with dependency on jsdom?
function XMLSerializer() {
}
XMLSerializer.prototype.serializeToString = function(node) {
var text = jsdom.serializeDocument(node);
// Fix a jsdom issue where all SVG tagNames are lowercased:
// https://github.com/tmpvar/jsdom/issues/620
var tagNames = ['linearGradient', 'radialGradient', 'clipPath'];
for (var i = 0, l = tagNames.length; i < l; i++) {
var tagName = tagNames[i];
text = text.replace(
new RegExp('(<|</)' + tagName.toLowerCase() + '\\b', 'g'),
function(all, start) {
return start + tagName;
});
}
return text;
};
function DOMParser() {
}
DOMParser.prototype.parseFromString = function(string, contenType) {
var div = document.createElement('div');
div.innerHTML = string;
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,21 +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');
/*#*/ }
/*#*/ if (__options.environment == 'node') {
/*#*/ include('node.js');
/*#*/ }
/*#*/ include('core/Base.js'); /*#*/ include('core/Base.js');
/*#*/ include('core/Emitter.js'); /*#*/ include('core/Emitter.js');
@ -68,7 +57,7 @@ var paper = new function(undefined) {
/*#*/ include('basic/Line.js'); /*#*/ include('basic/Line.js');
/*#*/ include('project/Project.js'); /*#*/ include('project/Project.js');
// /*#*/ include('project/Symbol.js'); /*#*/ include('project/Symbol.js');
/*#*/ include('item/Item.js'); /*#*/ include('item/Item.js');
/*#*/ include('item/Group.js'); /*#*/ include('item/Group.js');
@ -135,4 +124,4 @@ var paper = new function(undefined) {
/*#*/ include('export.js'); /*#*/ include('export.js');
return paper; return paper;
}; }(this.window);

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())
@ -135,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'