ncc
Version:
node-chrome-canvas | a simple to use and performant HTML5 canvas for Node.js
1,252 lines (1,078 loc) • 43.1 kB
text/typescript
/// <reference path="chrome-launcher.ts" />
var http = require('http')
, fs = require('fs')
, ws = require('ws')
, path = require('path');
var DEBUG = false;
var logger;
interface NCC {
(options?, callback?): Canvas;
options: any;
createCanvas(width?: number, height?: number): Canvas;
createImage(src?: string, onload?: Function, onerror?: Function): Image;
uid(type?: string): string;
log(msg: string, level?: number): void;
}
var NCC = Object.defineProperties(
(options_, callback_) => {
if (typeof (options_) == 'function') {
callback_ = options_;
options_ = null;
}
var callback = callback_;
if (options_)
for (var key in NCC.options)
if (options_[key] !== undefined)
NCC.options[key] = options_[key];
logger = require('tracer').colorConsole({
format: "[ncc] {{message}}",
level: NCC.options.logLevel
});
var canvas = NCC.createCanvas(undefined, undefined, true);
var attempts = 0;
function connect() {
var url = `http://localhost:${NCC.options.port}/json`;
http.get(url, res => {
var rdJson = '';
res.on('data', chunk => rdJson += chunk);
res.on('end', () => {
var ncc_ = JSON.parse(rdJson).find(i => i.title === "ncc" || path.basename(i.url) === "ncc.html");
if (!ncc_) {
if (attempts < NCC.options.retry) {
attempts++;
logger.info(`connecting [retry ${attempts}/${NCC.options.retry}]`);
setTimeout(connect, NCC.options.retryDelay);
} else logger.error('connection failed');
return;
}
Object.defineProperties(rdp, { ws: { value: new ws(ncc_.webSocketDebuggerUrl) } });
rdp.ws.on('open', () => {
logger.info("connected");
function checkReadyState() {
rdp.ws.once('message', (data) => {
data = JSON.parse(data);
if (!(data.result && data.result.result.value == "complete"))
return checkReadyState();
logger.info(`document.readyState is "complete"`);
rdp((err, res) => {
if (err)
logger.error(`[ncc] error: ${err.message}`);
if (callback)
err ? callback(err, null) : callback(null, canvas, rdp);
})
})
rdp.ws.send(`{"id":0,"method":"Runtime.evaluate", "params":{"expression":"document.readyState"}}`, err => err && checkReadyState());
}
checkReadyState()
});
rdp.ws.on('close', () => logger.info("session closed"));
})
})
}
var index = path.join(__dirname, 'ncc.html');
var launcher = new ChromeLauncher({
port: NCC.options.port,
autoSelectChrome: true,
startingUrl: NCC.options.headless ? index : '',
chromeFlags: NCC.options.headless ?
['--window-size=0,0', '--disable-gpu', '--headless'] :
[`--app=${index}`]
})
const exitHandler = (err) => {
rdp.ws.terminate()
launcher.kill().then(() => process.exit(-1));
};
process.on('SIGINT', exitHandler);
process.on('unhandledRejection', exitHandler);
process.on('rejectionHandled', exitHandler);
process.on('uncaughtException', exitHandler);
launcher.run()
.then((a) => {
logger.info("chrome started");
connect()
})
.catch(err => {
return launcher.kill().then(() => {
logger.error("failed starting chrome");
throw err;
}, logger.error);
});
return canvas;
}, {
options: {
enumerable: true,
writable: true,
value: {
logLevel: 'info',
port: 9222,
retry: 9,
retryDelay: 500,
headless: false
}
},
createCanvas: {
enumerable: true,
value: function (width?: number, height?: number, main?: boolean) {
if (!main) {
var uid = NCC.uid('canvas')
rdp(`var ${uid} = document.createElement('canvas')`);
}
var canvas: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, canvas);
} : undefined);
return canvas;
}
CanvasPDM._uid.value = main ? 'canvas' : uid;
Object.defineProperties(canvas, CanvasPDM);
CanvasPDM._uid.value = '';
canvas.width = width;
canvas.height = height;
return canvas;
}
},
createImage: {
enumerable: true,
value: function (src?: string, onload?: Function, onerror?: Function) {
var uid = NCC.uid('image')
rdp(`var ${uid} = new Image()`);
var image: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, image);
} : undefined);
return image;
}
ImagePDM._uid.value = uid;
Object.defineProperties(image, ImagePDM);
ImagePDM._uid.value = '';
image.src = src;
image.onload = onload;
image.onerror = onerror;
return image
}
},
uid: {
enumerable: false,
value: type => `${type}_${Math.random().toString(36).slice(2)}`
}
})
interface RDP {
(_?: any): RDP;
cmd: string;
queue: any[];
ws: any;
}
// RDP | Remote Debugging Protocol (the bridge to chrome)
var rdp = Object.defineProperties(
(_): RDP => {
if (typeof _ == 'string') {
logger.log(`< ${_}`);
rdp.cmd += `${_};`;
return rdp;
}
if (_ !== null) {
if (rdp.cmd === '') {
_();
return rdp;
}
rdp.queue.push({
cmd: rdp.cmd,
callback: _
});
rdp.cmd = '';
}
if (!rdp.queue[0] || rdp.req == rdp.queue[0] || !rdp.ws) return rdp;
rdp.req = rdp.queue[0];
logger.trace(`> ${rdp.req.cmd.split(';').join(';\n ')}`);
rdp.ws.once('message', data => {
data.error && logger.error(data.error)
!data.error && logger.log(data.result);
data = JSON.parse(data);
var err = data.error || data.result.wasThrown ? data.result.result.description : null,
res = err ? null : data.result.result;
if (rdp.req.callback) rdp.req.callback(err, res);
rdp.req = rdp.queue.shift();
rdp(null);
});
rdp.ws.send(`{"id":0,"method":"Runtime.evaluate", "params":{"expression":"${rdp.req.cmd}"}}`, err => err && rdp());
return rdp;
}, {
cmd: { enumerable: DEBUG, writable: true, value: '' },
queue: { enumerable: DEBUG, value: [] }
})
// Callback || abstract interface
interface Callback {
(callback?: Function): Callback;
}
// ProxyObj || abstract interface
interface ProxyObj extends Callback {
_uid: string;
_remote: any;
}
// Canvas
interface Canvas extends ProxyObj {
(callback?): Canvas;
_ctx: Context2d;
width: number;
height: number;
getContext: (contextId: string) => Context2d;
}
var CanvasPDM = {
// private properties
_uid: {
configurable: true,
enumerable: DEBUG,
value: "canvas"
},
_remote: {
enumerable: DEBUG,
set: function (null_) {
if (null_ === null) {
if (this._uid == 'canvas')
return logger.error('you cannot delete the main canvas')
rdp(`${this._uid} = null`);
Object.defineProperty(this, '_uid', { value: null });
this._ctx = null;
} else return logger.error('"_remote" can only be set to "null"')
}
},
_ctx: {
enumerable: DEBUG,
writable: true,
value: null
},
// Properties || proxies with defaults
width_: {
enumerable: DEBUG,
writable: true,
value: 300
},
height_: {
enumerable: DEBUG,
writable: true,
value: 150
},
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
// Properties || getters/setters || https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Properties
width: {
enumerable: true,
get: function () {
return this.width_;
},
set: function (width) {
if (width === undefined) return;
rdp(`${this._uid}.width = ${width}`)
return this.width_ = width;
}
},
height: {
enumerable: true,
get: function () {
return this.height_;
},
set: function (height) {
if (height === undefined) return;
rdp(`${this._uid}.height = ${height}`)
return this.height_ = height;
}
},
// Methods || https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Methods
getContext: {
enumerable: true,
value: function (contextId: string) {
if (contextId == '2d') {
var uid = this._uid == 'canvas' ? 'context2d' : NCC.uid('context2d')
rdp(`var ${uid} = ${this._uid}.getContext('2d')`);
var context2d: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, context2d);
} : undefined);
return context2d;
}
context2dPDM._uid.value = uid;
context2dPDM['canvas'].value = this;
Object.defineProperties(context2d, context2dPDM);
context2dPDM._uid.value = '';
return context2d;
} else
logger.error(`${contextId} is not implemented`);
}
},
toDataURL: {
enumerable: true,
value: function (type, args) {
rdp(`${this._uid}.toDataURL(${`'${type}'` || ''})`);
return (callback) => {
rdp((err, res) => {
if (err)
return callback(err, null);
callback(err, res.value);
});
};
}
}
};
// context2d
interface Context2d extends ProxyObj {
(callback?): Context2d;
canvas: Canvas;
fillStyle: any;
font: string;
globalAlpha: number;
globalCompositeOperation: string;
lineCap: string;
lineJoin: string;
lineWidth: number;
miterLimit: number;
shadowBlur: number;
shadowColor: string;
shadowOffsetX: number;
shadowOffsetY: number;
strokeStyle: any;
textAlign: string;
textBaseline: string;
webkitBackingStorePixelRatio: number;
webkitImageSmoothingEnabled: boolean;
arc(x: number, y: number, radius: number, startAngle?: number, endAngle?: number, anticlockwise?: boolean): Context2d;
arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): Context2d;
beginPath(): Context2d;
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): Context2d;
clearRect(x: number, y: number, w: number, h: number): Context2d;
clip(): Context2d;
closePath(): Context2d;
createImageData(imageDataOrSw: any, sh?: number): Context2d;
createLinearGradient(x0: number, y0: number, x1: number, y1: number): Context2d;
createPattern(image: HTMLElement, repetition: string): Context2d;
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): Context2d;
drawImage(image: HTMLElement, offsetX: number, offsetY: number, width?: number, height?: number, canvasOffsetX?: number, canvasOffsetY?: number, canvasImageWidth?: number, canvasImageHeight?: number): Context2d;
fill(): Context2d;
fillRect(x: number, y: number, w: number, h: number): Context2d;
fillText(text: string, x: number, y: number, maxWidth?: number): Context2d;
getImageData(sx: number, sy: number, sw: number, sh: number): Context2d;
getLineDash(): Context2d;
isPointInPath(x: number, y: number): Context2d;
isPointInStroke(x: number, y: number): Context2d;
lineTo(x: number, y: number): Context2d;
measureText(text: string): Context2d;
moveTo(x: number, y: number): Context2d;
putImageData(imagedata: ImageData, dx: number, dy: number, dirtyX?: number, dirtyY?: number, dirtyWidth?: number, dirtyHeight?: number): Context2d;
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): Context2d;
rect(x: number, y: number, w: number, h: number): Context2d;
restore(): Context2d;
rotate(angle: number): Context2d;
save(): Context2d;
scale(x: number, y: number): Context2d;
setLineDash(): Context2d;
setTransform(m11: number, m12: number, m21: number, m22: number, dx: number, dy: number): Context2d;
stroke(): Context2d;
strokeRect(x: number, y: number, w: number, h: number): Context2d;
strokeText(text: string, x: number, y: number, maxWidth?: number): Context2d;
transform(m11: number, m12: number, m21: number, m22: number, dx: number, dy: number): Context2d;
translate(x: number, y: number): Context2d;
}
var context2dPDM = {
// private properties
_uid: {
enumerable: DEBUG,
value: ''
},
_remote: {
enumerable: DEBUG,
set: function (null_) {
if (null_ === null) {
rdp(`${this._uid} = null`);
Object.defineProperty(this, '_uid', { value: null });
} else
logger.error('"_remote" can only be set to "null"')
}
},
// Attributes || proxies with defaults
fillStyle_: { writable: true, enumerable: DEBUG, value: '#000000' },
font_: { writable: true, enumerable: DEBUG, value: '10px sans-serif' },
globalAlpha_: { writable: true, enumerable: DEBUG, value: 1.0 },
globalCompositeOperation_: { writable: true, enumerable: DEBUG, value: 'source-over' },
lineCap_: { writable: true, enumerable: DEBUG, value: 'butt' },
lineDashOffset_: { writable: true, enumerable: DEBUG, value: 0 },
lineJoin_: { writable: true, enumerable: DEBUG, value: 'miter' },
lineWidth_: { writable: true, enumerable: DEBUG, value: 1.0 },
miterLimit_: { writable: true, enumerable: DEBUG, value: 10 },
shadowBlur_: { writable: true, enumerable: DEBUG, value: 0 },
shadowColor_: { writable: true, enumerable: DEBUG, value: 'rgba(0, 0, 0, 0)' },
shadowOffsetX_: { writable: true, enumerable: DEBUG, value: 0 },
shadowOffsetY_: { writable: true, enumerable: DEBUG, value: 0 },
strokeStyle_: { writable: true, enumerable: DEBUG, value: '#000000' },
textAlign_: { writable: true, enumerable: DEBUG, value: 'start' },
textBaseline_: { writable: true, enumerable: DEBUG, value: 'alphabetic' },
webkitBackingStorePixelRatio_: { writable: true, enumerable: DEBUG, value: 1 },
webkitImageSmoothingEnabled_: { writable: true, enumerable: DEBUG, value: true },
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d
// Attributes || getters/setters || https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d#Attributes
canvas: {
enumerable: true, value: null // will be overridden on creation
},
fillStyle: {
enumerable: true, get: function () { return this.fillStyle_; },
set: function (fillStyle) {
rdp(`${this._uid}.fillStyle = ${fillStyle._uid || `'${fillStyle}'`}`);
return this.fillStyle_ = fillStyle;
}
},
font: {
enumerable: true, get: function () { return this.font_; },
set: function (font) {
rdp(`${this._uid}.font = '${font}'`);
return this.font_ = font;
}
},
globalAlpha: {
enumerable: true, get: function () { return this.globalAlpha_; },
set: function (globalAlpha) {
rdp(`${this._uid}.globalAlpha = ${globalAlpha}`);
return this.globalAlpha_ = globalAlpha;
}
},
globalCompositeOperation: {
enumerable: true, get: function () { return this.globalCompositeOperation_; },
set: function (globalCompositeOperation) {
rdp(`${this._uid}.globalCompositeOperation = '${globalCompositeOperation}'`);
return this.globalCompositeOperation_ = globalCompositeOperation;
}
},
lineCap: {
enumerable: true, get: function () { return this.lineCap_; },
set: function (lineCap) {
rdp(`${this._uid}.lineCap = '${lineCap}'`);
return this.lineCap_ = lineCap;
}
},
lineDashOffset: {
enumerable: true, get: function () { return this.lineDashOffset_; },
set: function (lineDashOffset) {
rdp(`${this._uid}.lineDashOffset = ${lineDashOffset}`);
return this.lineDashOffset_ = lineDashOffset;
}
},
lineJoin: {
enumerable: true, get: function () { return this.lineJoin_; },
set: function (lineJoin) {
rdp(`${this._uid}.lineJoin = '${lineJoin}'`);
return this.lineJoin_ = lineJoin;
}
},
lineWidth: {
enumerable: true, get: function () { return this.lineWidth_; },
set: function (lineWidth) {
rdp(`${this._uid}.lineWidth = ${lineWidth}`);
return this.lineWidth_ = lineWidth;
}
},
miterLimit: {
enumerable: true, get: function () { return this.miterLimit_; },
set: function (miterLimit) {
rdp(`${this._uid}.miterLimit = ${miterLimit}`);
return this.miterLimit_ = miterLimit;
}
},
shadowBlur: {
enumerable: true, get: function () { return this.shadowBlur_; },
set: function (shadowBlur) {
rdp(`${this._uid}.shadowBlur = ${shadowBlur}`);
return this.shadowBlur_ = shadowBlur;
}
},
shadowColor: {
enumerable: true, get: function () { return this.shadowColor; },
set: function (shadowColor) {
rdp(`${this._uid}.shadowColor = '${shadowColor}'`);
return this.shadowColor_ = shadowColor;
}
},
shadowOffsetX: {
enumerable: true, get: function () { return this.shadowOffsetX_; },
set: function (shadowOffsetX) {
rdp(`${this._uid}.shadowOffsetX = ${shadowOffsetX}`);
return this.shadowOffsetX_ = shadowOffsetX;
}
},
shadowOffsetY: {
enumerable: true, get: function () { return this.shadowOffsetY_; },
set: function (shadowOffsetY) {
rdp(`${this._uid}.shadowOffsetY = ${shadowOffsetY}`);
return this.shadowOffsetY_ = shadowOffsetY;
}
},
strokeStyle: {
enumerable: true, get: function () { return this.strokeStyle_; },
set: function (strokeStyle) {
rdp(`${this._uid}.strokeStyle = ${strokeStyle._uid || `'${strokeStyle}'`}`);
return this.strokeStyle_ = strokeStyle;
}
},
textAlign: {
enumerable: true, get: function () { return this.textAlign_; },
set: function (textAlign) {
rdp(`${this._uid}.textAlign = '${textAlign}'`);
return this.textAlign_ = textAlign;
}
},
textBaseline: {
enumerable: true, get: function () { return this.textBaseline_; },
set: function (textBaseline) {
rdp(`${this._uid}.textBaseline = '${textBaseline}'`);
return this.textBaseline_ = textBaseline;
}
},
webkitBackingStorePixelRatio: {
enumerable: true, get: function () { return this.webkitBackingStorePixelRatio_; },
set: function (webkitBackingStorePixelRatio) {
rdp(`${this._uid}.webkitBackingStorePixelRatio = ${webkitBackingStorePixelRatio}`);
return this.webkitBackingStorePixelRatio_ = webkitBackingStorePixelRatio;
}
},
webkitImageSmoothingEnabled: {
enumerable: true, get: function () { return this.webkitImageSmoothingEnabled_; },
set: function (webkitImageSmoothingEnabled) {
rdp(`${this._uid}.webkitImageSmoothingEnabled = ${webkitImageSmoothingEnabled}`);
return this.webkitImageSmoothingEnabled_ = webkitImageSmoothingEnabled;
}
},
// Methods || https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingcontext2d#Methods
arc: { //RETURN/ void //IN/ in float x, in float y, in float radius, in float startAngle, in float endAngle, in boolean anticlockwise Optional
enumerable: true,
value: function (x, y, radius, startAngle, endAngle, anticlockwise) {
return rdp(`${this._uid}.arc(${Array.prototype.slice.call(arguments, 0).join(',')})`);
}
},
arcTo: { //RETURN/ void //IN/ in float x1, in float y1, in float x2, in float y2, in float radius
enumerable: true,
value: function (x1, y1, x2, y2, radius) {
return rdp(`${this._uid}.arcTo(${x1},${y1},${x2},${y2},${radius})`);
}
},
beginPath: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.beginPath()`);
}
},
bezierCurveTo: { //RETURN/ void //IN/ in float cp1x, in float cp1y, in float cp2x, in float cp2y, in float x, in float y
enumerable: true,
value: function (cp1x, cp1y, cp2x, cp2y, x, y) {
return rdp(`${this._uid}.bezierCurveTo(${cp1x},${cp1y},${cp2x},${cp2y},${x},${y})`);
}
},
clearRect: { //RETURN/ void //IN/ in float x, in float y, in float width, in float height
enumerable: true,
value: function (x, y, width, height) {
return rdp(`${this._uid}.clearRect(${x},${y},${width},${height})`);
}
},
clip: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.clip()`);
}
},
closePath: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.closePath()`);
}
},
createImageData: { //RETURN/ ImageData //IN/ in float width, in float height
enumerable: true,
value: function (width, height) {
if (width.height != undefined) { // if image data is passed
height = width.height;
width = width.width;
}
return (callback) => {
callback(null, {
data: new Uint8ClampedArray(Array.apply(null, new Array(width * height * 4)).map(Number.prototype.valueOf, 0)),
width: width,
height: height
});
};
}
},
createLinearGradient: { //RETURN/ nsIDOMCanvasGradient //IN/ in float x0, in float y0, in float x1, in float y1
enumerable: true,
value: function (x0, y0, x1, y1) {
var uid = NCC.uid('linearGradient')
rdp(`var ${uid} = ${this._uid}.createLinearGradient(${x0},${y0},${x1},${y1})`);
var linearGradient: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, linearGradient);
} : undefined);
return linearGradient;
}
GradientPDM._uid.value = uid;
Object.defineProperties(linearGradient, GradientPDM);
GradientPDM._uid.value = '';
return linearGradient;
}
},
createPattern: { //RETURN/ nsIDOMCanvasPattern //IN/ in nsIDOMHTMLElement image, in DOMString repetition
enumerable: true,
value: function (image, repetition) {
var uid = NCC.uid('pattern');
rdp(`var ${uid} = ${this._uid}.createPattern(${image._uid},'${repetition}')`);
var pattern: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, pattern);
} : undefined);
return pattern;
}
PatternPDM._uid.value = uid;
Object.defineProperties(pattern, PatternPDM);
PatternPDM._uid.value = '';
return pattern;
}
},
createRadialGradient: { //RETURN/ nsIDOMCanvasGradient //IN/ in float x0, in float y0, in float r0, in float x1, in float y1, in float r1
enumerable: true,
value: function (x0, y0, r0, x1, y1, r1) {
var uid = NCC.uid('pattern')
rdp(`var ${uid} = ${this._uid}.createRadialGradient(${x0},${y0},${r0},${x1},${y1},${r1})`);
var radialGradient: any = (callback?) => {
rdp(callback ? (err, res) => {
err ? callback(err, null) : callback(null, radialGradient);
} : undefined);
return radialGradient;
}
GradientPDM._uid.value = NCC.uid('radialGradient');
Object.defineProperties(radialGradient, GradientPDM);
GradientPDM._uid.value = '';
return radialGradient;
}
},
drawImage: { //RETURN/ void //IN/ in nsIDOMElement image, in float a1, in float a2, in float a3 Optional, in float a4 Optional, in float a5 Optional, in float a6 Optional, in float a7 Optional, in float a8 Optional
enumerable: true,
value: function (image, a1, a2, a3, a4, a5, a6, a7, a8) {
return rdp(`${this._uid}.drawImage(${image._uid}, ${Array.prototype.slice.call(arguments, 1).join(',')})`);
}
},
// no use
//drawCustomFocusRing: { //RETURN/ boolean //IN/ Element element
// enumerable:true,
// value: function (element) {
// rdp(`${this._uid}.drawCustomFocusRing(" + element + ")`);
// return this;
// }
//},
// no use
//drawSystemFocusRing: { //RETURN/ void //IN/ Element element
// enumerable:true,
// value: function (element) {
// rdp(`${this._uid}.drawSystemFocusRinelementg()`);
// return this;
// }
//},
fill: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.fill()`);
}
},
fillRect: { //RETURN/ void //IN/ in float x, in float y, in float width, in float height
enumerable: true,
value: function (x, y, width, height) {
return rdp(`${this._uid}.fillRect(${x},${y},${width},${height})`);
}
},
fillText: { //RETURN/ void //IN/ in DOMString text, in float x, in float y, in float maxWidth Optional
enumerable: true,
value: function (text, x, y, maxWidth) {
return rdp(`${this._uid}.fillText('${text}',${Array.prototype.slice.call(arguments, 1).join(',')})`);
}
},
getImageData: { //RETURN/ //IN/ in float x, in float y, in float width, in float height
enumerable: true,
value: function (x, y, width, height) {
rdp(`Array.prototype.slice.call(${this._uid}.getImageData(${x},${y},${width},${height}).data).join(',')`);
return (callback) => {
rdp((err, res) => {
if (err)
return callback(err, null);
var imageData = {
data: new Uint8ClampedArray(res.value.split(',')),
width: width,
height: height
};
callback(null, imageData);
});
};
}
},
getLineDash: { //RETURN/ sequence <unrestricted double> //IN/
enumerable: true,
value: function () {
rdp(`${this._uid}.getLineDash().join(',')`);
return (callback) => {
rdp((err, res) => {
if (err)
return callback(err);
res.value = res.value.split(',');
for (var i = 0, l = res.value.length; i < l; i++)
res.value[i] = +res.value[i];
callback(err, res.value);
});
};
}
},
isPointInPath: { //RETURN/ boolean //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
rdp(`${this._uid}.isPointInPath(${x},${y})`);
return (callback) => {
rdp((err, res) => {
callback(err, res.value);
});
};
}
},
isPointInStroke: { //RETURN/ boolean //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
rdp(`${this._uid}.isPointInStroke(${x},${y})`);
return (callback) => {
rdp((err, res) => {
callback(err, res.value);
});
};
}
},
lineTo: { //RETURN/ void //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
return rdp(`${this._uid}.lineTo(${x},${y})`);
}
},
measureText: { //RETURN/ nsIDOMTextMetrics //IN/ in DOMString text
enumerable: true,
value: function (text) {
rdp(`${this._uid}.measureText('${text}').width`);
return (callback) => {
rdp((err, res) => {
if (err)
return callback(err);
callback(null, { width: res.value });
});
};
}
},
moveTo: { //RETURN/ void //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
return rdp(`${this._uid}.moveTo(${x},${y})`);
}
},
putImageData: { //RETURN/ void //IN/ in ImageData imagedata, in float dx, double dy, in float dirtyX Optional, in float dirtyY Optional, in float dirtyWidth Optional, in float dirtyHeight Optional
enumerable: true,
value: function (imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
return rdp(`var data = [${Array.prototype.slice.call(imagedata.data).join(',')}]; var iD = ${this._uid}.createImageData(${imagedata.width}, ${imagedata.height}); for (var i = 0, l = iD.data.length; i < l; i++) iD.data[i] = +data[i]; ${this._uid}.putImageData(iD, ${Array.prototype.slice.call(arguments, 1).join(',')})`);
}
},
quadraticCurveTo: { //RETURN/ void //IN/ in float cpx, in float cpy, in float x, in float y
enumerable: true,
value: function (cpx, cpy, x, y) {
return rdp(`${this._uid}.quadraticCurveTo(${cpx},${cpy},${x},${y})`);
}
},
rect: { //RETURN/ void //IN/ in float x, in float y, in float width, in float height
enumerable: true,
value: function (x, y, width, height) {
return rdp(`${this._uid}.rect(${x},${y},${width},${height})`);
}
},
restore: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.restore()`);
}
},
rotate: { //RETURN/ void //IN/ in float angle
enumerable: true,
value: function (angle) {
return rdp(`${this._uid}.rotate(${angle})`);
}
},
save: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.save()`);
}
},
scale: { //RETURN/ void //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
return rdp(`${this._uid}.scale(${x},${y})`);
}
},
// no use
//scrollPathIntoView: { //RETURN/ void //IN/
// enumerable: true,
// value: function () {
// rdp(`${this._uid}.scrollPathIntoView()`);
// return this;
// }
//},
setLineDash: { //RETURN/ void //IN/ in sequence <unrestricted double> segments
enumerable: true,
value: function (segments) {
return rdp(`${this._uid}.setLineDash([${segments.join(',')}])`);
}
},
setTransform: { //RETURN/ void //IN/ in float m11, in float m12, in float m21, in float m22, in float dx, in float dy
enumerable: true,
value: function (m11, m12, m21, m22, dx, dy) {
return rdp(`${this._uid}.setTransform(${m11},${m12},${m21},${m22},${dx},${dy})`);
}
},
stroke: { //RETURN/ void //IN/
enumerable: true,
value: function () {
return rdp(`${this._uid}.stroke()`);
}
},
strokeRect: { //RETURN/ void //IN/ in float x, in float y, in float w, in float h
enumerable: true,
value: function (x, y, w, h) {
return rdp(`${this._uid}.strokeRect(${x},${y},${w},${h})`);
}
},
strokeText: { //RETURN/ void //IN/ in DOMString text, in float x, in float y, in float maxWidth Optional
enumerable: true,
value: function (text, x, y, maxWidth) {
rdp(`${this._uid}.strokeText('${text}',${(Array.prototype.slice.call(arguments, 1).join(','))})`);
return this;
}
},
transform: { //RETURN/ void //IN/ in float m11, in float m12, in float m21, in float m22, in float dx, in float dy
enumerable: true,
value: function (m11, m12, m21, m22, dx, dy) {
return rdp(`${this._uid}.transform(${m11},${m12},${m21},${m22},${dx},${dy})`);
}
},
translate: { //RETURN/ void //IN/ in float x, in float y
enumerable: true,
value: function (x, y) {
return rdp(`${this._uid}.translate(${x},${y})`);
}
}
};
// Gradient
interface Gradient extends ProxyObj {
(callback?): Gradient;
addColorStop(offset: number, color: string): Callback;
}
var GradientPDM = {
// private properties
_uid: {
enumerable: DEBUG,
value: ''
},
_remote: {
enumerable: DEBUG,
set: function (null_) {
if (null_ === null) {
rdp(`${this._uid} = null`);
Object.defineProperty(this, '_uid', { value: null });
} else
logger.error('"_remote" can only be set to "null"')
}
},
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/CanvasGradient
// Methods
addColorStop: {
enumerable: true,
value: function (offset, color) {
return rdp(`${this._uid}.addColorStop(${offset},'${color}')`);
}
}
};
// Pattern
interface Pattern extends ProxyObj {
(callback?): Pattern;
}
var PatternPDM = {
// private properties
_uid: {
enumerable: DEBUG,
value: ''
},
_remote: {
enumerable: DEBUG,
set: function (null_) {
if (null_ === null) {
rdp(`${this._uid} = null`);
Object.defineProperty(this, '_uid', { value: null });
} else
logger.error('"_remote" can only be set to "null"')
}
},
// Web API: https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern
};
// Image
interface Image extends ProxyObj {
(callback?): Image;
_base64: string;
_toFile(): any;
src: string;
onload(): any;
onerror(): any;
width: number;
height: number;
}
var mimeMap = {
png: 'image/png',
webp: 'image/webp',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
svg: 'image/svg+xml',
gif: 'image/gif'
};
var regExp_http = new RegExp('^(http:\\/\\/.+)', 'i');
var regExp_data = new RegExp('^(data:image\\/\\w+;base64,.+)');
var regExp_type = new RegExp('^data:image\\/(\\w+);base64,');
var ImagePDM = {
// private properties
_uid: {
enumerable: DEBUG,
value: ''
},
_remote: {
enumerable: DEBUG,
set: function (null_) {
if (null_ === null) {
rdp(`${this._uid} = null`);
Object.defineProperty(this, '_uid', { value: null });
} else
logger.error('"_remote" can only be set to "null"')
}
},
// Properties
src_: {
enumerable: DEBUG,
writable: true,
value: ''
},
width_: {
enumerable: DEBUG,
writable: true,
value: undefined
},
height_: {
enumerable: DEBUG,
writable: true,
value: undefined
},
_base64_: {
enumerable: DEBUG,
writable: true,
value: null
},
_base64: {
enumerable: DEBUG,
get: function () {
return this._base64_;
},
set: function (base64) {
rdp(`${this._uid}.src = '${base64}'`);
rdp(() => {
rdp(`${this._uid}.width + '_' + ${this._uid}.height`);
rdp((err, res) => {
if (err && this.onerror)
return this.onerror(err);
var size = res.value.split('_');
this.width_ = +size[0];
this.height_ = +size[1];
if (this.onload)
return this.onload(this);
});
});
this._base64_ = base64;
return this._base64_;
}
},
// Methods
_toFile: {
enumerable: DEBUG,
value: function (filename, callback) {
var head = regExp_type.exec(this._base64_),
type = filename.split('.').pop();
if (!head || !head[1] || (head[1] != ((type == "jpg") ? "jpeg" : type)))
if (callback) return callback(`type mismatch ${head ? head[1] : "'unknown'"} !> ${type}`);
else throw new Error(`type mismatch ${head ? head[1] : "'unknown'"} !> ${type}`)
logger.info(`[ncc] writing image to: ${filename}`);
fs.writeFile(filename, new Buffer(this._base64_.replace(/^data:image\/\w+;base64,/, ''), 'base64'), {}, callback);
}
},
// Web API
// Properties
src: {
enumerable: true,
get: function () {
return this.src_;
},
set: function (src) {
var img = this;
this._src = src;
if (!src || src === '') return;
if (regExp_data.test(src)) img._base64 = src;
else if (regExp_http.test(src)) {
logger.info(`[ncc] loading image from URL: ${src}`);
http.get(src, function (res) {
var data = '';
res.setEncoding('base64');
if (res.statusCode != 200) {
if (img.onerror) return img.onerror(`loading image failed with status ${res.statusCode}`);
else logger.error(`loading image failed with status ${res.statusCode}`);
}
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
img._base64 = `data:${(res.headers["content-type"] || mimeMap[src.split('.').pop()])};base64,${data}`;
logger.info('[ncc] loading image from URL completed');
});
}).on('error', this.onerror || function (err) {
if (img.onerror) return img.onerror(err);
else logger.error(`loading image failed with err ${err}`);
});
} else {
logger.info(`[ncc] loading image from FS: ${src}`);
fs.readFile(src, 'base64', function (err, data) {
if (err) {
if (img.onerror) img.onerror(err);
else logger.error(`loading image failed with err ${err}`);
}
img._base64 = `data:${mimeMap[src.split('.').pop()]};base64,${data}`;
logger.info('[ncc] loading image from FS completed');
});
}
return this.src_;
}
},
onload: {
writable: true,
enumerable: true,
value: undefined
},
onerror: {
writable: true,
enumerable: true,
value: undefined
},
width: {
enumerable: true,
get: function () {
return this.width_;
}
},
height: {
enumerable: true,
get: function () {
return this.height_;
}
}
};
module.exports = NCC;