UNPKG

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
/// <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;