diff --git a/Readme.md b/Readme.md index b27549962..7dce02e91 100644 --- a/Readme.md +++ b/Readme.md @@ -97,7 +97,7 @@ This project is an implementation of the Web Canvas API and implements that API ### createCanvas() > ```ts -> createCanvas(width: number, height: number, type?: 'PDF'|'SVG') => Canvas +> createCanvas(width: number, height: number, type?: 'fbdev'|'pdf'|'svg') => Canvas > ``` Creates a Canvas instance. This method works in both Node.js and Web browsers, where there is no Canvas constructor. (See `browser.js` for the implementation that runs in browsers.) diff --git a/binding.gyp b/binding.gyp index 777032c0b..841e59ee2 100644 --- a/binding.gyp +++ b/binding.gyp @@ -62,6 +62,7 @@ 'src/backend/Backend.cc', 'src/backend/ImageBackend.cc', 'src/backend/PdfBackend.cc', + 'src/backend/ScreenBackend.cc', 'src/backend/SvgBackend.cc', 'src/bmp/BMPParser.cc', 'src/Backends.cc', @@ -145,6 +146,11 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' } }], + ['OS=="linux" and has_FBDev=="true"', + { + 'defines': ['HAS_FBDEV'], + 'sources': ['src/backend/FBDevBackend.cc'] + }], ['with_jpeg=="true"', { 'defines': [ 'HAVE_JPEG' @@ -216,7 +222,10 @@ }] ] }] - ] + ], + 'variables': { + 'has_FBDev%': 'true', + } } ] } diff --git a/examples/simple_fbdev.js b/examples/simple_fbdev.js new file mode 100644 index 000000000..f8b440322 --- /dev/null +++ b/examples/simple_fbdev.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +/** +* Module dependencies. +*/ + +const fs = require('fs') +const { join } = require('path') + +const { backends: { FBDevBackend }, Canvas } = require('..') + +const squareSize = 100 + +var device = process.argv[2] + +var backend = new FBDevBackend(device) +var canvas = new Canvas(backend) +var ctx = canvas.getContext('2d') + +var offsetX = canvas.width - squareSize +var offsetY = canvas.height - squareSize + +ctx.fillStyle = '#FF0000' +ctx.fillRect(0, 0, squareSize, squareSize) + +ctx.fillStyle = '#00FF00' +ctx.fillRect(offsetX, 0, squareSize, squareSize) + +ctx.fillStyle = '#0000FF' +ctx.fillRect(0, offsetY, squareSize, squareSize) + +ctx.fillStyle = '#FFFFFF' +ctx.fillRect(offsetX, offsetY, squareSize, squareSize) + +console.log('Width: ' + canvas.width + ', Height: ' + canvas.height + + 'Pixel format: ' + ctx.pixelFormat) + +var outPath = join(__dirname, 'rectangle.png') + +canvas.createPNGStream().pipe(fs.createWriteStream(outPath)) diff --git a/lib/DOMMatrix.js b/lib/DOMMatrix.js index 379c78c7a..17a54f82e 100644 --- a/lib/DOMMatrix.js +++ b/lib/DOMMatrix.js @@ -4,7 +4,7 @@ const util = require('util') // DOMMatrix per https://drafts.fxtf.org/geometry/#DOMMatrix -function DOMPoint(x, y, z, w) { +function DOMPoint (x, y, z, w) { if (!(this instanceof DOMPoint)) { throw new TypeError("Class constructors cannot be invoked without 'new'") } @@ -22,15 +22,15 @@ function DOMPoint(x, y, z, w) { } // Constants to index into _values (col-major) -const M11 = 0, M12 = 1, M13 = 2, M14 = 3 -const M21 = 4, M22 = 5, M23 = 6, M24 = 7 -const M31 = 8, M32 = 9, M33 = 10, M34 = 11 -const M41 = 12, M42 = 13, M43 = 14, M44 = 15 +const M11 = 0; const M12 = 1; const M13 = 2; const M14 = 3 +const M21 = 4; const M22 = 5; const M23 = 6; const M24 = 7 +const M31 = 8; const M32 = 9; const M33 = 10; const M34 = 11 +const M41 = 12; const M42 = 13; const M43 = 14; const M44 = 15 const DEGREE_PER_RAD = 180 / Math.PI const RAD_PER_DEGREE = Math.PI / 180 -function parseMatrix(init) { +function parseMatrix (init) { var parsed = init.replace(/matrix\(/, '') parsed = parsed.split(/,/, 7) // 6 + 1 to handle too many params if (parsed.length !== 6) throw new Error(`Failed to parse ${init}`) @@ -43,14 +43,14 @@ function parseMatrix(init) { ] } -function parseMatrix3d(init) { +function parseMatrix3d (init) { var parsed = init.replace(/matrix3d\(/, '') parsed = parsed.split(/,/, 17) // 16 + 1 to handle too many params if (parsed.length !== 16) throw new Error(`Failed to parse ${init}`) return parsed.map(parseFloat) } -function parseTransform(tform) { +function parseTransform (tform) { var type = tform.split(/\(/, 1)[0] switch (type) { case 'matrix': @@ -161,15 +161,15 @@ DOMMatrix.prototype[util.inspect.custom || 'inspect'] = function (depth, options } DOMMatrix.prototype.toString = function () { - return this.is2D ? - `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})` : - `matrix3d(${this._values.join(', ')})` + return this.is2D + ? `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})` + : `matrix3d(${this._values.join(', ')})` } /** * Checks that `value` is a number and sets the value. */ -function setNumber2D(receiver, index, value) { +function setNumber2D (receiver, index, value) { if (typeof value !== 'number') throw new TypeError('Expected number') return receiver._values[index] = value } @@ -178,7 +178,7 @@ function setNumber2D(receiver, index, value) { * Checks that `value` is a number, sets `_is2D = false` if necessary and sets * the value. */ -function setNumber3D(receiver, index, value) { +function setNumber3D (receiver, index, value) { if (typeof value !== 'number') throw new TypeError('Expected number') if (index === M33 || index === M44) { if (value !== 1) receiver._is2D = false @@ -187,31 +187,31 @@ function setNumber3D(receiver, index, value) { } Object.defineProperties(DOMMatrix.prototype, { - m11: {get: function () { return this._values[M11] }, set: function (v) { return setNumber2D(this, M11, v) }}, - m12: {get: function () { return this._values[M12] }, set: function (v) { return setNumber2D(this, M12, v) }}, - m13: {get: function () { return this._values[M13] }, set: function (v) { return setNumber3D(this, M13, v) }}, - m14: {get: function () { return this._values[M14] }, set: function (v) { return setNumber3D(this, M14, v) }}, - m21: {get: function () { return this._values[M21] }, set: function (v) { return setNumber2D(this, M21, v) }}, - m22: {get: function () { return this._values[M22] }, set: function (v) { return setNumber2D(this, M22, v) }}, - m23: {get: function () { return this._values[M23] }, set: function (v) { return setNumber3D(this, M23, v) }}, - m24: {get: function () { return this._values[M24] }, set: function (v) { return setNumber3D(this, M24, v) }}, - m31: {get: function () { return this._values[M31] }, set: function (v) { return setNumber3D(this, M31, v) }}, - m32: {get: function () { return this._values[M32] }, set: function (v) { return setNumber3D(this, M32, v) }}, - m33: {get: function () { return this._values[M33] }, set: function (v) { return setNumber3D(this, M33, v) }}, - m34: {get: function () { return this._values[M34] }, set: function (v) { return setNumber3D(this, M34, v) }}, - m41: {get: function () { return this._values[M41] }, set: function (v) { return setNumber2D(this, M41, v) }}, - m42: {get: function () { return this._values[M42] }, set: function (v) { return setNumber2D(this, M42, v) }}, - m43: {get: function () { return this._values[M43] }, set: function (v) { return setNumber3D(this, M43, v) }}, - m44: {get: function () { return this._values[M44] }, set: function (v) { return setNumber3D(this, M44, v) }}, - - a: {get: function () { return this.m11 }, set: function (v) { return this.m11 = v }}, - b: {get: function () { return this.m12 }, set: function (v) { return this.m12 = v }}, - c: {get: function () { return this.m21 }, set: function (v) { return this.m21 = v }}, - d: {get: function () { return this.m22 }, set: function (v) { return this.m22 = v }}, - e: {get: function () { return this.m41 }, set: function (v) { return this.m41 = v }}, - f: {get: function () { return this.m42 }, set: function (v) { return this.m42 = v }}, - - is2D: {get: function () { return this._is2D }}, // read-only + m11: { get: function () { return this._values[M11] }, set: function (v) { return setNumber2D(this, M11, v) } }, + m12: { get: function () { return this._values[M12] }, set: function (v) { return setNumber2D(this, M12, v) } }, + m13: { get: function () { return this._values[M13] }, set: function (v) { return setNumber3D(this, M13, v) } }, + m14: { get: function () { return this._values[M14] }, set: function (v) { return setNumber3D(this, M14, v) } }, + m21: { get: function () { return this._values[M21] }, set: function (v) { return setNumber2D(this, M21, v) } }, + m22: { get: function () { return this._values[M22] }, set: function (v) { return setNumber2D(this, M22, v) } }, + m23: { get: function () { return this._values[M23] }, set: function (v) { return setNumber3D(this, M23, v) } }, + m24: { get: function () { return this._values[M24] }, set: function (v) { return setNumber3D(this, M24, v) } }, + m31: { get: function () { return this._values[M31] }, set: function (v) { return setNumber3D(this, M31, v) } }, + m32: { get: function () { return this._values[M32] }, set: function (v) { return setNumber3D(this, M32, v) } }, + m33: { get: function () { return this._values[M33] }, set: function (v) { return setNumber3D(this, M33, v) } }, + m34: { get: function () { return this._values[M34] }, set: function (v) { return setNumber3D(this, M34, v) } }, + m41: { get: function () { return this._values[M41] }, set: function (v) { return setNumber2D(this, M41, v) } }, + m42: { get: function () { return this._values[M42] }, set: function (v) { return setNumber2D(this, M42, v) } }, + m43: { get: function () { return this._values[M43] }, set: function (v) { return setNumber3D(this, M43, v) } }, + m44: { get: function () { return this._values[M44] }, set: function (v) { return setNumber3D(this, M44, v) } }, + + a: { get: function () { return this.m11 }, set: function (v) { return this.m11 = v } }, + b: { get: function () { return this.m12 }, set: function (v) { return this.m12 = v } }, + c: { get: function () { return this.m21 }, set: function (v) { return this.m21 = v } }, + d: { get: function () { return this.m22 }, set: function (v) { return this.m22 = v } }, + e: { get: function () { return this.m41 }, set: function (v) { return this.m41 = v } }, + f: { get: function () { return this.m42 }, set: function (v) { return this.m42 = v } }, + + is2D: { get: function () { return this._is2D } }, // read-only isIdentity: { get: function () { @@ -229,7 +229,7 @@ Object.defineProperties(DOMMatrix.prototype, { * @param {Float64Array} values Value to assign to `_values`. This is assigned * without copying (okay because all usages are followed by a multiply). */ -function newInstance(values) { +function newInstance (values) { var instance = Object.create(DOMMatrix.prototype) instance.constructor = DOMMatrix instance._is2D = true @@ -237,7 +237,7 @@ function newInstance(values) { return instance } -function multiply(A, B) { +function multiply (A, B) { var dest = new Float64Array(16) for (var i = 0; i < 4; i++) { for (var j = 0; j < 4; j++) { @@ -386,10 +386,10 @@ DOMMatrix.prototype.rotateAxisAngleSelf = function (x, y, z, angle) { // NB: This is the generic transform. If the axis is a major axis, there are // faster transforms. this._values = multiply([ - tx * x + c, tx * y + s * z, tx * z - s * y, 0, - tx * y - s * z, ty * y + c, ty * z + s * x, 0, - tx * z + s * y, ty * z - s * x, t * z * z + c, 0, - 0, 0, 0, 1 + tx * x + c, tx * y + s * z, tx * z - s * y, 0, + tx * y - s * z, ty * y + c, ty * z + s * x, 0, + tx * z + s * y, ty * z - s * x, t * z * z + c, 0, + 0, 0, 0, 1 ], this._values) if (x !== 0 || y !== 0) this._is2D = false return this @@ -606,4 +606,4 @@ DOMMatrix.prototype.toFloat64Array = function () { return this._values.slice(0) } -module.exports = {DOMMatrix, DOMPoint} +module.exports = { DOMMatrix, DOMPoint } diff --git a/lib/bindings.js b/lib/bindings.js index c0afc9841..95ee914f3 100644 --- a/lib/bindings.js +++ b/lib/bindings.js @@ -1,3 +1,3 @@ -'use strict'; +'use strict' -module.exports = require('../build/Release/canvas.node'); +module.exports = require('../build/Release/canvas.node') diff --git a/lib/canvas.js b/lib/canvas.js index 18b47364d..b1e0be5c8 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas @@ -21,18 +21,18 @@ Canvas.prototype[util.inspect.custom || 'inspect'] = function () { } Canvas.prototype.getContext = function (contextType, contextAttributes) { - if ('2d' == contextType) { - var ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes)); - this.context = ctx; - ctx.canvas = this; - return ctx; + if (contextType == '2d') { + var ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes)) + this.context = ctx + ctx.canvas = this + return ctx } -}; +} Canvas.prototype.pngStream = -Canvas.prototype.createPNGStream = function(options){ - return new PNGStream(this, options); -}; +Canvas.prototype.createPNGStream = function (options) { + return new PNGStream(this, options) +} Canvas.prototype.pdfStream = Canvas.prototype.createPDFStream = function(options){ @@ -40,9 +40,9 @@ Canvas.prototype.createPDFStream = function(options){ }; Canvas.prototype.jpegStream = -Canvas.prototype.createJPEGStream = function(options){ - return new JPEGStream(this, options); -}; +Canvas.prototype.createJPEGStream = function (options) { + return new JPEGStream(this, options) +} Canvas.prototype.toDataURL = function(a1, a2, a3){ // valid arg patterns (args -> [type, opts, fn]): @@ -63,51 +63,51 @@ Canvas.prototype.toDataURL = function(a1, a2, a3){ // ['image/jpeg', opts] -> ['image/jpeg', opts, fn] // ['image/jpeg', qual] -> ['image/jpeg', {quality: qual}, fn] - var type = 'image/png'; - var opts = {}; - var fn; + var type = 'image/png' + var opts = {} + var fn - if ('function' === typeof a1) { - fn = a1; + if (typeof a1 === 'function') { + fn = a1 } else { - if ('string' === typeof a1 && FORMATS.includes(a1.toLowerCase())) { - type = a1.toLowerCase(); + if (typeof a1 === 'string' && FORMATS.includes(a1.toLowerCase())) { + type = a1.toLowerCase() } - if ('function' === typeof a2) { - fn = a2; + if (typeof a2 === 'function') { + fn = a2 } else { - if ('object' === typeof a2) { - opts = a2; - } else if ('number' === typeof a2) { - opts = {quality: Math.max(0, Math.min(1, a2))}; + if (typeof a2 === 'object') { + opts = a2 + } else if (typeof a2 === 'number') { + opts = { quality: Math.max(0, Math.min(1, a2)) } } - if ('function' === typeof a3) { - fn = a3; + if (typeof a3 === 'function') { + fn = a3 } else if (undefined !== a3) { - throw new TypeError(typeof a3 + ' is not a function'); + throw new TypeError(typeof a3 + ' is not a function') } } } if (this.width === 0 || this.height === 0) { // Per spec, if the bitmap has no pixels, return this string: - var str = "data:,"; + var str = 'data:,' if (fn) { - setTimeout(() => fn(null, str)); - return; + setTimeout(() => fn(null, str)) + return } else { - return str; + return str } } if (fn) { this.toBuffer((err, buf) => { - if (err) return fn(err); - fn(null, `data:${type};base64,${buf.toString('base64')}`); + if (err) return fn(err) + fn(null, `data:${type};base64,${buf.toString('base64')}`) }, type, opts) } else { return `data:${type};base64,${this.toBuffer(type, opts).toString('base64')}` } -}; +} diff --git a/lib/image.js b/lib/image.js index 2788a5d93..a09bcc8e0 100644 --- a/lib/image.js +++ b/lib/image.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - Image @@ -30,14 +30,14 @@ Object.defineProperty(Image.prototype, 'src', { * @param {String|Buffer} val filename, buffer, data URI, URL * @api public */ - set(val) { + set (val) { if (typeof val === 'string') { if (/^\s*data:/.test(val)) { // data: URI const commaI = val.indexOf(',') // 'base64' must come before the comma const isBase64 = val.lastIndexOf('base64', commaI) !== -1 const content = val.slice(commaI + 1) - setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val); + setSource(this, Buffer.from(content, isBase64 ? 'base64' : 'utf8'), val) } else if (/^\s*https?:\/\//.test(val)) { // remote URL const onerror = err => { if (typeof this.onerror === 'function') { @@ -59,20 +59,20 @@ Object.defineProperty(Image.prototype, 'src', { setSource(this, data) }) } else { // local file path assumed - setSource(this, val); + setSource(this, val) } } else if (Buffer.isBuffer(val)) { - setSource(this, val); + setSource(this, val) } }, - get() { + get () { // TODO https://github.com/Automattic/node-canvas/issues/118 - return getSource(this); + return getSource(this) }, configurable: true -}); +}) // TODO || is for Node.js pre-v6.6.0 Image.prototype[util.inspect.custom || 'inspect'] = function(){ diff --git a/lib/jpegstream.js b/lib/jpegstream.js index a057d8983..6bf5e9a8b 100644 --- a/lib/jpegstream.js +++ b/lib/jpegstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - JPEGStream @@ -11,35 +11,35 @@ var util = require('util'); var JPEGStream = module.exports = function JPEGStream(canvas, options) { if (!(this instanceof JPEGStream)) { - throw new TypeError("Class constructors cannot be invoked without 'new'"); + throw new TypeError("Class constructors cannot be invoked without 'new'") } if (canvas.streamJPEGSync === undefined) { - throw new Error("node-canvas was built without JPEG support."); + throw new Error('node-canvas was built without JPEG support.') } - Readable.call(this); + Readable.call(this) - this.options = options; - this.canvas = canvas; -}; + this.options = options + this.canvas = canvas +} -util.inherits(JPEGStream, Readable); +util.inherits(JPEGStream, Readable) -function noop() {} +function noop () {} -JPEGStream.prototype._read = function _read() { +JPEGStream.prototype._read = function _read () { // For now we're not controlling the c++ code's data emission, so we only // call canvas.streamJPEGSync once and let it emit data at will. - this._read = noop; - var self = this; - self.canvas.streamJPEGSync(this.options, function(err, chunk){ + this._read = noop + var self = this + self.canvas.streamJPEGSync(this.options, function (err, chunk) { if (err) { - self.emit('error', err); + self.emit('error', err) } else if (chunk) { - self.push(chunk); + self.push(chunk) } else { - self.push(null); + self.push(null) } - }); -}; + }) +} diff --git a/lib/parse-font.js b/lib/parse-font.js index ce03529e0..43454e3e9 100644 --- a/lib/parse-font.js +++ b/lib/parse-font.js @@ -5,11 +5,16 @@ */ const weights = 'bold|bolder|lighter|[1-9]00' - , styles = 'italic|oblique' - , variants = 'small-caps' - , stretches = 'ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded' - , units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q' - , string = '\'([^\']+)\'|"([^"]+)"|[\\w\\s-]+' + +const styles = 'italic|oblique' + +const variants = 'small-caps' + +const stretches = 'ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded' + +const units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q' + +const string = '\'([^\']+)\'|"([^"]+)"|[\\w\\s-]+' // [ [ <‘font-style’> || || <‘font-weight’> || <‘font-stretch’> ]? // <‘font-size’> [ / <‘line-height’> ]? <‘font-family’> ] @@ -19,8 +24,8 @@ const styleRe = new RegExp('(' + styles + ') +', 'i') const variantRe = new RegExp('(' + variants + ') +', 'i') const stretchRe = new RegExp('(' + stretches + ') +', 'i') const sizeFamilyRe = new RegExp( - '([\\d\\.]+)(' + units + ') *' - + '((?:' + string + ')( *, *(?:' + string + '))*)') + '([\\d\\.]+)(' + units + ') *' + + '((?:' + string + ')( *, *(?:' + string + '))*)') /** * Cache font parsing. diff --git a/lib/pdfstream.js b/lib/pdfstream.js index 6aeec53d7..e40187c50 100644 --- a/lib/pdfstream.js +++ b/lib/pdfstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - PDFStream @@ -9,31 +9,31 @@ var util = require('util'); var PDFStream = module.exports = function PDFStream(canvas, options) { if (!(this instanceof PDFStream)) { - throw new TypeError("Class constructors cannot be invoked without 'new'"); + throw new TypeError("Class constructors cannot be invoked without 'new'") } - Readable.call(this); + Readable.call(this) this.canvas = canvas; this.options = options; }; -util.inherits(PDFStream, Readable); +util.inherits(PDFStream, Readable) -function noop() {} +function noop () {} -PDFStream.prototype._read = function _read() { +PDFStream.prototype._read = function _read () { // For now we're not controlling the c++ code's data emission, so we only // call canvas.streamPDFSync once and let it emit data at will. - this._read = noop; - var self = this; - self.canvas.streamPDFSync(function(err, chunk, len){ + this._read = noop + var self = this + self.canvas.streamPDFSync(function (err, chunk, len) { if (err) { - self.emit('error', err); + self.emit('error', err) } else if (len) { - self.push(chunk); + self.push(chunk) } else { - self.push(null); + self.push(null) } }, this.options); }; diff --git a/lib/pngstream.js b/lib/pngstream.js index 021bb7fd9..8e6f7f909 100644 --- a/lib/pngstream.js +++ b/lib/pngstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - PNGStream @@ -9,38 +9,38 @@ var Readable = require('stream').Readable; var util = require('util'); -var PNGStream = module.exports = function PNGStream(canvas, options) { +var PNGStream = module.exports = function PNGStream (canvas, options) { if (!(this instanceof PNGStream)) { - throw new TypeError("Class constructors cannot be invoked without 'new'"); + throw new TypeError("Class constructors cannot be invoked without 'new'") } - Readable.call(this); + Readable.call(this) if (options && options.palette instanceof Uint8ClampedArray && options.palette.length % 4 !== 0) { - throw new Error("Palette length must be a multiple of 4."); + throw new Error('Palette length must be a multiple of 4.') } - this.canvas = canvas; - this.options = options || {}; -}; + this.canvas = canvas + this.options = options || {} +} -util.inherits(PNGStream, Readable); +util.inherits(PNGStream, Readable) -function noop() {} +function noop () {} -PNGStream.prototype._read = function _read() { +PNGStream.prototype._read = function _read () { // For now we're not controlling the c++ code's data emission, so we only // call canvas.streamPNGSync once and let it emit data at will. - this._read = noop; - var self = this; - self.canvas.streamPNGSync(function(err, chunk, len){ + this._read = noop + var self = this + self.canvas.streamPNGSync(function (err, chunk, len) { if (err) { - self.emit('error', err); + self.emit('error', err) } else if (len) { - self.push(chunk); + self.push(chunk) } else { - self.push(null); + self.push(null) } - }, self.options); -}; + }, self.options) +} diff --git a/package.json b/package.json index 78b31f82b..8f5c17d09 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "prebenchmark": "node-gyp build", "benchmark": "node benchmarks/run.js", "lint": "standard examples/*.js test/server.js test/public/*.js benchmarks/run.js lib/context2d.js util/has_lib.js browser.js index.js", + "pretest": "npm run lint && node-gyp build", "test": "mocha test/*.test.js", "pretest-server": "node-gyp build", "test-server": "node test/server.js", @@ -53,13 +54,14 @@ "simple-get": "^3.0.3" }, "devDependencies": { - "@types/node": "^10.12.18", + "@types/node": "^14.11.10", "assert-rejects": "^1.0.0", - "dtslint": "^0.5.3", - "express": "^4.16.3", - "mocha": "^5.2.0", - "pixelmatch": "^4.0.2", - "standard": "^12.0.1" + "dtslint": "^4.0.4", + "express": "^4.17.1", + "mocha": "^7.1.1", + "pixelmatch": "^5.1.0", + "standard": "^14.3.3", + "typescript": "^4.0.3" }, "engines": { "node": ">=6" diff --git a/src/Backends.cc b/src/Backends.cc index 2256c32b6..169955b54 100644 --- a/src/Backends.cc +++ b/src/Backends.cc @@ -4,6 +4,10 @@ #include "backend/PdfBackend.h" #include "backend/SvgBackend.h" +#ifdef HAS_FBDEV +#include "backend/FBDevBackend.h" +#endif + using namespace v8; void Backends::Initialize(Local target) { @@ -14,5 +18,9 @@ void Backends::Initialize(Local target) { PdfBackend::Initialize(obj); SvgBackend::Initialize(obj); + #ifdef HAS_FBDEV + FBDevBackend::Initialize(obj); + #endif + Nan::Set(target, Nan::New("Backends").ToLocalChecked(), obj).Check(); } diff --git a/src/Canvas.cc b/src/Canvas.cc index 17d6d828d..1924be138 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -30,6 +30,10 @@ #include "backend/PdfBackend.h" #include "backend/SvgBackend.h" +#ifdef HAS_FBDEV +#include "backend/FBDevBackend.h" +#endif + #define GENERIC_FACE_ERROR \ "The second argument to registerFont is required, and should be an object " \ "with at least a family (string) and optionally weight (string/number) " \ @@ -106,8 +110,26 @@ NAN_METHOD(Canvas::New) { backend = new PdfBackend(width, height); else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) backend = new SvgBackend(width, height); +#ifdef HAS_FBDEV + else if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { + if (info[3]->IsString()) { + if(info[4]->IsBoolean()) { + if(info[5]->IsBoolean()) + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + Nan::To(info[4]).FromMaybe(0), Nan::To(info[5]).FromMaybe(0)); + else + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + Nan::To(info[4]).FromMaybe(0)); + } + else + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); + } + else + backend = new FBDevBackend(width, height); + } +#endif else - backend = new ImageBackend(width, height); + return Nan::ThrowRangeError("Unknown canvas type"); } else backend = new ImageBackend(width, height); @@ -115,7 +137,11 @@ NAN_METHOD(Canvas::New) { else if (info[0]->IsObject()) { if (Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || Nan::New(PdfBackend::constructor)->HasInstance(info[0]) || - Nan::New(SvgBackend::constructor)->HasInstance(info[0])) { + Nan::New(SvgBackend::constructor)->HasInstance(info[0]) +#ifdef HAS_FBDEV + || Nan::New(FBDevBackend::constructor)->HasInstance(info[0]) +#endif + ) { backend = Nan::ObjectWrap::Unwrap(Nan::To(info[0]).ToLocalChecked()); }else{ return Nan::ThrowTypeError("Invalid arguments"); @@ -503,7 +529,14 @@ NAN_METHOD(Canvas::ToBuffer) { // Async JPEG if (info[0]->IsFunction() && info[1]->StrictEquals(jpegStr)) { - JpegClosure* closure = new JpegClosure(canvas); + JpegClosure* closure; + try { + closure = new JpegClosure(canvas); + } catch (cairo_status_t ex) { + Nan::ThrowError(Canvas::Error(ex)); + return; + } + parseJPEGArgs(info[2], *closure); canvas->Ref(); @@ -895,8 +928,6 @@ Canvas::resurface(Local canvas) { Nan::HandleScope scope; Local context; - backend()->recreateSurface(); - // Reset context context = Nan::Get(canvas, Nan::New("context").ToLocalChecked()).ToLocalChecked(); if (!context->IsUndefined()) { diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index a78fecd5c..b53fe0d9e 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -1,8 +1,10 @@ #include "Backend.h" #include + Backend::Backend(std::string name, int width, int height) : name(name) + , format(CAIRO_FORMAT_INVALID) , width(width) , height(height) {} @@ -30,11 +32,11 @@ void Backend::setCanvas(Canvas* _canvas) } -cairo_surface_t* Backend::recreateSurface() +void Backend::recreateSurface() { this->destroySurface(); - return this->createSurface(); + this->createSurface(); } DLL_PUBLIC cairo_surface_t* Backend::getSurface() { @@ -77,6 +79,16 @@ void Backend::setHeight(int height_) this->recreateSurface(); } +cairo_format_t Backend::getFormat() +{ + return this->format; +} +void Backend::setFormat(cairo_format_t format) +{ + this->format = format; + this->recreateSurface(); +} + bool Backend::isSurfaceValid(){ bool hadSurface = surface != NULL; bool isValid = true; diff --git a/src/backend/Backend.h b/src/backend/Backend.h index c0c4e1928..4db39a670 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -18,10 +18,16 @@ class Backend : public Nan::ObjectWrap protected: int width; int height; + cairo_format_t format; cairo_surface_t* surface = nullptr; Canvas* canvas = nullptr; - Backend(std::string name, int width, int height); + Backend(std::string name, int width = 0, int height = 0); + + virtual void createSurface() = 0; + virtual void destroySurface(); + virtual void recreateSurface(); + static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } @@ -30,12 +36,7 @@ class Backend : public Nan::ObjectWrap void setCanvas(Canvas* canvas); - virtual cairo_surface_t* createSurface() = 0; - virtual cairo_surface_t* recreateSurface(); - DLL_PUBLIC cairo_surface_t* getSurface(); - virtual void destroySurface(); - DLL_PUBLIC std::string getName(); DLL_PUBLIC int getWidth(); @@ -45,9 +46,8 @@ class Backend : public Nan::ObjectWrap virtual void setHeight(int height); // Overridden by ImageBackend. SVG and PDF thus always return INVALID. - virtual cairo_format_t getFormat() { - return CAIRO_FORMAT_INVALID; - } + virtual cairo_format_t getFormat(); + virtual void setFormat(cairo_format_t format); bool isSurfaceValid(); inline const char* getError(){ return error; } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc new file mode 100644 index 000000000..32b3f69ee --- /dev/null +++ b/src/backend/FBDevBackend.cc @@ -0,0 +1,363 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "FBDevBackend.h" + + +using namespace v8; + + +// switch through bpp and decide on which format for the cairo surface to use +cairo_format_t bits2format(__u32 bits_per_pixel) +{ + switch(bits_per_pixel) + { + case 16: return CAIRO_FORMAT_RGB16_565; + case 24: return CAIRO_FORMAT_RGB24; + case 32: return CAIRO_FORMAT_ARGB32; + + default: + throw FBDevBackendException("Only valid formats are RGB16_565, RGB24 and ARGB32"); + } +} + + +FBDevBackend::FBDevBackend(int width, int height, string deviceName, + bool useDoubleBuffer, bool enableFlipPages) + : ScreenBackend("fbdev", width, height) + , fb_data(NULL) + , back_buffer(NULL) + , useDoubleBuffer(useDoubleBuffer) + , useInMemoryBackBuffer(false) + , useFlipPages(false) + , enableFlipPages(enableFlipPages) +{ + struct fb_var_screeninfo fb_vinfo; + + this->initFbDev(deviceName, &fb_vinfo); + + fb_vinfo.xres = width; + fb_vinfo.yres = height; + + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, + "Error setting variable framebuffer information"); +} + +FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, + bool enableFlipPages) + : ScreenBackend("fbdev") + , fb_data(NULL) + , back_buffer(NULL) + , useDoubleBuffer(useDoubleBuffer) + , useInMemoryBackBuffer(false) + , useFlipPages(false) + , enableFlipPages(enableFlipPages) +{ + struct fb_var_screeninfo fb_vinfo; + + this->initFbDev(deviceName, &fb_vinfo); + + this->width = fb_vinfo.xres; + this->height = fb_vinfo.yres; +} + +FBDevBackend::~FBDevBackend() +{ + this->destroySurface(); + + close(this->fb_fd); +} + + +void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo) +{ + // Open the file for reading and writing + this->fb_fd = open(deviceName.c_str(), O_RDWR); + + if(this->fb_fd == -1) + { + std::ostringstream o; + o << "cannot open framebuffer device \"" << deviceName << "\""; + throw FBDevBackendException(o.str()); + } + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, + "Error reading variable framebuffer information"); + + this->format = bits2format(fb_vinfo->bits_per_pixel); +} + + +void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, + string errmsg) +{ + if(ioctl(this->fb_fd, request, data) == -1) + throw FBDevBackendException(errmsg.c_str()); +} + + +void FBDevBackend::createSurface() +{ + destroySurface(); + + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); + + // Reset vertical panning + fb_vinfo.yoffset = 0; + ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo); + + // Check support for double buffering features + if(useDoubleBuffer || fb_vinfo.bits_per_pixel == 24) + { + useFlipPages = false; + + // Try to use page flipping inside graphic card memory. If FbDev driver + // don't support to have a virtual framebuffer bigger than the actual one, + // then we'll use a buffer in memory. It's not so efficient and could lead + // to some tearing, but with VSync this should be minimal and at least we'll + // not see screen redraws. + fb_vinfo.yres_virtual = fb_vinfo.yres * 2; + + // Adjust virtual framebuffer width to hold RGB24 Cairo surface in 24 bits + fb_vinfo.xres_virtual = fb_vinfo.bits_per_pixel == 24 + ? ceil(fb_vinfo.xres*4/3) + : fb_vinfo.xres; + + useInMemoryBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, &fb_vinfo) == -1; + + if(enableFlipPages && !useInMemoryBackBuffer && fb_vinfo.bits_per_pixel != 24) + { + // Try to use page flipping inside graphic card memory. If FbDev driver + // don't support vertical panning, then we'll need to copy data from back + // buffer. It's not so efficient and could lead to some tearing, but with + // VSync this should be minimal and at least we'll not see screen redraws. + fb_vinfo.yoffset = fb_vinfo.yres; + + useFlipPages = ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == 0; + } + } + + // Map the device to memory with new virtual framebuffer dimensions and config + this->FbDevIoctlHelper(FBIOGET_FSCREENINFO, &fb_finfo, + "Error reading fixed framebuffer information"); + + this->fb_data = (unsigned char*) mmap(0, fb_finfo.smem_len, + PROT_READ | PROT_WRITE, MAP_SHARED, this->fb_fd, 0); + + if(this->fb_data == MAP_FAILED) + throw FBDevBackendException("Failed to map framebuffer device to memory"); + + // Set pointers of the front and back buffers + front_buffer = this->fb_data; + + if(!useDoubleBuffer && fb_vinfo.bits_per_pixel != 24) + back_buffer = front_buffer; + + else if(useInMemoryBackBuffer) + back_buffer = (unsigned char*)calloc(fb_vinfo.yres, stride); + + else + { + // back buffer in graphic card memory + back_buffer = front_buffer + fb_vinfo.yres * fb_finfo.line_length; + + if(useFlipPages) + { + // Swap front and back buffers since vertical panning checking was + // succesful (so for the graphic card they were already swapped) + front_buffer = back_buffer; + back_buffer = this->fb_data; + } + } + + // create cairo surface from data + this->surface = cairo_image_surface_create_for_data(this->back_buffer, + format, fb_vinfo.xres, fb_vinfo.yres, stride); +} + +void FBDevBackend::destroySurface() +{ + ScreenBackend::destroySurface(); + + if(useInMemoryBackBuffer && back_buffer) + { + free(back_buffer); + + back_buffer = NULL; + } + + if(this->fb_data) + { + munmap(this->fb_data, fb_finfo.smem_len); + + this->fb_data = NULL; + } +} + + +void FBDevBackend::setWidth(int width) +{ + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + fb_vinfo.xres = width; + + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, + "Error setting variable framebuffer information"); + + ScreenBackend::setWidth(width); +} +void FBDevBackend::setHeight(int height) +{ + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + fb_vinfo.yres = height; + + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, + "Error setting variable framebuffer information"); + + ScreenBackend::setHeight(height); +} +void FBDevBackend::setFormat(cairo_format_t format) +{ + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + switch(format) + { + case CAIRO_FORMAT_RGB16_565: fb_vinfo.bits_per_pixel = 16; break; + case CAIRO_FORMAT_RGB24: fb_vinfo.bits_per_pixel = 24; break; + case CAIRO_FORMAT_ARGB32: fb_vinfo.bits_per_pixel = 32; break; + + default: + throw FBDevBackendException("Only valid formats are RGB16_565, RGB24 and ARGB32"); + } + + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, + "Error setting variable framebuffer information"); + + ScreenBackend::setFormat(format); +} + + +void FBDevBackend::copyBackBuffer(struct fb_var_screeninfo* fb_vinfo) +{ + if(fb_vinfo->bits_per_pixel != 24) + memcpy(front_buffer, back_buffer, fb_vinfo->yres * fb_finfo.line_length); + + else + { + int stride = cairo_format_stride_for_width(format, fb_vinfo->xres); + + for(unsigned int y=0; yyres; y++) + for(unsigned int x=0; xxres; x++) + memcpy(front_buffer + y*fb_finfo.line_length + x*3, + back_buffer + y*stride + x*4 + 1, + 3); + } +} +void FBDevBackend::flipPages(struct fb_var_screeninfo* fb_vinfo) +{ + if(!this->surface) return; + + // Update display panning + fb_vinfo->yoffset = fb_vinfo->yoffset ? 0 : fb_vinfo->yres; + + this->FbDevIoctlHelper(FBIOPAN_DISPLAY, fb_vinfo, + "Error panning the framebuffer display"); + + // Swap front and back buffers pointers + unsigned char* aux = front_buffer; + front_buffer = back_buffer; + back_buffer = aux; + + // Destroy Cairo surface and create it in the new back buffer vertical offset + cairo_surface_destroy(this->surface); + + this->surface = cairo_image_surface_create_for_data(this->back_buffer, + format, fb_vinfo->xres, fb_vinfo->yres, fb_finfo.line_length); +} + +void FBDevBackend::swapBuffers() +{ + if(!this->surface) return; + + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + if(!useDoubleBuffer && fb_vinfo.bits_per_pixel != 24) return; + + if(useFlipPages) + flipPages(&fb_vinfo); + + else + copyBackBuffer(&fb_vinfo); +} + +void FBDevBackend::waitVSync() +{ + int arg = 0; + + this->FbDevIoctlHelper(FBIO_WAITFORVSYNC, &arg, + "Error waiting for framebuffer VSync"); +} + + +Nan::Persistent FBDevBackend::constructor; + +void FBDevBackend::Initialize(Local target) +{ + Nan::HandleScope scope; + + Local ctor = Nan::New(FBDevBackend::New); + FBDevBackend::constructor.Reset(ctor); + ctor->InstanceTemplate()->SetInternalFieldCount(1); + ctor->SetClassName(Nan::New("FBDevBackend").ToLocalChecked()); + + ScreenBackend::Initialize(ctor); + + Nan::Set(target, + Nan::New("FBDevBackend").ToLocalChecked(), + Nan::GetFunction(ctor).ToLocalChecked()).Check(); +} + +NAN_METHOD(FBDevBackend::New) +{ + string fbDevice = DEFAULT_DEVICE; + if(info[0]->IsString()) fbDevice = *Nan::Utf8String(info[0]); + + bool useDoubleBuffer = false; + if(info[1]->IsBoolean()) useDoubleBuffer = Nan::To(info[1]).FromMaybe(0); + + bool enableFlipPages = false; + if(info[2]->IsBoolean()) enableFlipPages = Nan::To(info[2]).FromMaybe(0); + + FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer, + enableFlipPages); + + backend->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); +} diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h new file mode 100644 index 000000000..8f0a07db6 --- /dev/null +++ b/src/backend/FBDevBackend.h @@ -0,0 +1,73 @@ +#ifndef __FBDEV_BACKEND_H__ +#define __FBDEV_BACKEND_H__ + +#include +#include + +#include +#include + +#include "ScreenBackend.h" + + +using namespace std; + + +const string DEFAULT_DEVICE = "/dev/fb0"; + + +class FBDevBackend : public ScreenBackend +{ + private: + int fb_fd; + struct fb_fix_screeninfo fb_finfo; + unsigned char* fb_data; + unsigned char* front_buffer; + unsigned char* back_buffer; + bool useDoubleBuffer; + bool useInMemoryBackBuffer; + bool useFlipPages; + bool enableFlipPages; + + ~FBDevBackend(); + + void initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo); + void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); + + void createSurface(); + void destroySurface(); + + void setWidth(int width); + void setHeight(int height); + void setFormat(cairo_format_t format); + + void copyBackBuffer(struct fb_var_screeninfo* fb_vinfo); + void flipPages(struct fb_var_screeninfo* fb_vinfo); + void swapBuffers(); + void waitVSync(); + + public: + FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE, + bool useDoubleBuffer = false, bool enableFlipPages = false); + FBDevBackend(string deviceName, bool useDoubleBuffer = false, + bool enableFlipPages = false); + + static Nan::Persistent constructor; + static void Initialize(v8::Local target); + static NAN_METHOD(New); +}; + + +class FBDevBackendException : public std::exception +{ + private: + string err_msg; + + public: + FBDevBackendException(const string msg) : err_msg(msg) {}; + ~FBDevBackendException() throw() {}; + + const char *what() const throw() { return this->err_msg.c_str(); }; +}; + +#endif diff --git a/src/backend/ImageBackend.cc b/src/backend/ImageBackend.cc index d354d92cc..d9dc2fb42 100644 --- a/src/backend/ImageBackend.cc +++ b/src/backend/ImageBackend.cc @@ -31,20 +31,25 @@ int32_t ImageBackend::approxBytesPerPixel() { } } -cairo_surface_t* ImageBackend::createSurface() { - assert(!surface); - surface = cairo_image_surface_create(format, width, height); - assert(surface); +void ImageBackend::createSurface() +{ + assert(!this->surface); + this->surface = cairo_image_surface_create(this->format, width, height); + assert(this->surface); Nan::AdjustExternalMemory(approxBytesPerPixel() * width * height); - return surface; } -void ImageBackend::destroySurface() { - if (surface) { - cairo_surface_destroy(surface); - surface = nullptr; - Nan::AdjustExternalMemory(-approxBytesPerPixel() * width * height); - } +void ImageBackend::recreateSurface() +{ + // Re-surface + if (this->surface) { + int old_width = cairo_image_surface_get_width(this->surface); + int old_height = cairo_image_surface_get_height(this->surface); + this->destroySurface(); + Nan::AdjustExternalMemory(-approxBytesPerPixel() * old_width * old_height); + } + + createSurface(); } cairo_format_t ImageBackend::getFormat() { diff --git a/src/backend/ImageBackend.h b/src/backend/ImageBackend.h index f68dacfdb..9d4df5efc 100644 --- a/src/backend/ImageBackend.h +++ b/src/backend/ImageBackend.h @@ -6,8 +6,8 @@ class ImageBackend : public Backend { private: - cairo_surface_t* createSurface(); - void destroySurface(); + void createSurface(); + void recreateSurface(); cairo_format_t format = DEFAULT_FORMAT; public: diff --git a/src/backend/PdfBackend.cc b/src/backend/PdfBackend.cc index d8bd23422..d19565543 100644 --- a/src/backend/PdfBackend.cc +++ b/src/backend/PdfBackend.cc @@ -21,16 +21,14 @@ Backend *PdfBackend::construct(int width, int height){ return new PdfBackend(width, height); } -cairo_surface_t* PdfBackend::createSurface() { +void PdfBackend::createSurface() { if (!_closure) _closure = new PdfSvgClosure(canvas); + surface = cairo_pdf_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height); - return surface; } -cairo_surface_t* PdfBackend::recreateSurface() { +void PdfBackend::recreateSurface() { cairo_pdf_surface_set_size(surface, width, height); - - return surface; } diff --git a/src/backend/PdfBackend.h b/src/backend/PdfBackend.h index 03656f500..15e00b150 100644 --- a/src/backend/PdfBackend.h +++ b/src/backend/PdfBackend.h @@ -7,8 +7,8 @@ class PdfBackend : public Backend { private: - cairo_surface_t* createSurface(); - cairo_surface_t* recreateSurface(); + void createSurface(); + void recreateSurface(); public: PdfSvgClosure* _closure = NULL; diff --git a/src/backend/ScreenBackend.cc b/src/backend/ScreenBackend.cc new file mode 100644 index 000000000..00ff35a3e --- /dev/null +++ b/src/backend/ScreenBackend.cc @@ -0,0 +1,53 @@ +#include "ScreenBackend.h" + + +using Nan::AsyncQueueWorker; +using Nan::AsyncWorker; +using Nan::Callback; + + +class WaitVSync: public AsyncWorker +{ + public: + WaitVSync(Callback* callback, ScreenBackend* backend) + : AsyncWorker(callback, "ScreenBackend:WaitVSync") + , backend(backend) + {} + + void Execute() + { + backend->waitVSync(); + } + + private: + ScreenBackend* backend; +}; + + +ScreenBackend::ScreenBackend(std::string name, int width, int height) + : Backend(name, width, height) +{} + + +NAN_METHOD(ScreenBackend::swapBuffers) +{ + ScreenBackend* backend = Nan::ObjectWrap::Unwrap(info.This()); + + backend->swapBuffers(); +} + +NAN_METHOD(ScreenBackend::waitVSync) +{ + ScreenBackend* backend = Nan::ObjectWrap::Unwrap(info.This()); + + Callback* callback = new Callback(info[0].As()); + + AsyncQueueWorker(new WaitVSync(callback, backend)); +} + + +void ScreenBackend::Initialize(Local ctor) +{ + Nan::SetPrototypeMethod(ctor, "swapBuffers", swapBuffers); + Nan::SetPrototypeMethod(ctor, "waitVSync", waitVSync); +} diff --git a/src/backend/ScreenBackend.h b/src/backend/ScreenBackend.h new file mode 100644 index 000000000..36ec32bea --- /dev/null +++ b/src/backend/ScreenBackend.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Backend.h" + + +using namespace v8; + + +class ScreenBackend : public Backend +{ + friend class WaitVSync; + + private: + virtual void swapBuffers(){}; + virtual void waitVSync(){}; + + static NAN_METHOD(swapBuffers); + static NAN_METHOD(waitVSync); + + protected: + ScreenBackend(std::string name, int width = 0, int height = 0); + + static void Initialize(Local ctor); +}; diff --git a/src/backend/SvgBackend.cc b/src/backend/SvgBackend.cc index 10bf4caa7..da5b8a113 100644 --- a/src/backend/SvgBackend.cc +++ b/src/backend/SvgBackend.cc @@ -25,20 +25,19 @@ Backend *SvgBackend::construct(int width, int height){ return new SvgBackend(width, height); } -cairo_surface_t* SvgBackend::createSurface() { - assert(!_closure); - _closure = new PdfSvgClosure(canvas); +void SvgBackend::createSurface() { + if (!_closure) _closure = new PdfSvgClosure(canvas); + surface = cairo_svg_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height); - return surface; } -cairo_surface_t* SvgBackend::recreateSurface() { +void SvgBackend::recreateSurface() { cairo_surface_finish(surface); delete _closure; _closure = nullptr; cairo_surface_destroy(surface); - return createSurface(); + createSurface(); } diff --git a/src/backend/SvgBackend.h b/src/backend/SvgBackend.h index 6377b438b..e8ab3a80c 100644 --- a/src/backend/SvgBackend.h +++ b/src/backend/SvgBackend.h @@ -7,8 +7,8 @@ class SvgBackend : public Backend { private: - cairo_surface_t* createSurface(); - cairo_surface_t* recreateSurface(); + void createSurface(); + void recreateSurface(); public: PdfSvgClosure* _closure = NULL; diff --git a/test/canvas.test.js b/test/canvas.test.js index b824f8da2..0a26cdbc5 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -21,79 +21,81 @@ describe('Canvas', function () { // afterEach(gc); it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { - const Canvas = require('../').Canvas; - var c = new Canvas(10, 10); - assert.throws(function () { Canvas.prototype.width; }, /incompatible receiver/); - assert(!c.hasOwnProperty('width')); - assert('width' in c); - assert(Canvas.prototype.hasOwnProperty('width')); - }); + const Canvas = require('../').Canvas + var c = new Canvas(10, 10) + assert.throws(function () { Canvas.prototype.width }, /incompatible receiver/) + assert(!c.hasOwnProperty('width')) + assert('width' in c) + assert(Canvas.prototype.hasOwnProperty('width')) + }) it('.parseFont()', function () { var tests = [ - '20px Arial' - , { size: 20, unit: 'px', family: 'Arial' } - , '20pt Arial' - , { size: 26.666666666666668, unit: 'pt', family: 'Arial' } - , '20.5pt Arial' - , { size: 27.333333333333332, unit: 'pt', family: 'Arial' } - , '20% Arial' - , { size: 20, unit: '%', family: 'Arial' } // TODO I think this is a bad assertion - ZB 23-Jul-2017 - , '20mm Arial' - , { size: 75.59055118110237, unit: 'mm', family: 'Arial' } - , '20px serif' - , { size: 20, unit: 'px', family: 'serif' } - , '20px sans-serif' - , { size: 20, unit: 'px', family: 'sans-serif' } - , '20px monospace' - , { size: 20, unit: 'px', family: 'monospace' } - , '50px Arial, sans-serif' - , { size: 50, unit: 'px', family: 'Arial,sans-serif' } - , 'bold italic 50px Arial, sans-serif' - , { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial,sans-serif' } - , '50px Helvetica , Arial, sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica,Arial,sans-serif' } - , '50px "Helvetica Neue", sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica Neue,sans-serif' } - , '50px "Helvetica Neue", "foo bar baz" , sans-serif' - , { size: 50, unit: 'px', family: 'Helvetica Neue,foo bar baz,sans-serif' } - , "50px 'Helvetica Neue'" - , { size: 50, unit: 'px', family: 'Helvetica Neue' } - , 'italic 20px Arial' - , { size: 20, unit: 'px', style: 'italic', family: 'Arial' } - , 'oblique 20px Arial' - , { size: 20, unit: 'px', style: 'oblique', family: 'Arial' } - , 'normal 20px Arial' - , { size: 20, unit: 'px', style: 'normal', family: 'Arial' } - , '300 20px Arial' - , { size: 20, unit: 'px', weight: '300', family: 'Arial' } - , '800 20px Arial' - , { size: 20, unit: 'px', weight: '800', family: 'Arial' } - , 'bolder 20px Arial' - , { size: 20, unit: 'px', weight: 'bolder', family: 'Arial' } - , 'lighter 20px Arial' - , { size: 20, unit: 'px', weight: 'lighter', family: 'Arial' } - , 'normal normal normal 16px Impact' - , { size: 16, unit: 'px', weight: 'normal', family: 'Impact', style: 'normal', variant: 'normal' } - , 'italic small-caps bolder 16px cursive' - , { size: 16, unit: 'px', style: 'italic', variant: 'small-caps', weight: 'bolder', family: 'cursive' } - , '20px "new century schoolbook", serif' - , { size: 20, unit: 'px', family: 'new century schoolbook,serif' } - , '20px "Arial bold 300"' // synthetic case with weight keyword inside family - , { size: 20, unit: 'px', family: 'Arial bold 300', variant: 'normal' } - ]; + '20px Arial', + { size: 20, unit: 'px', family: 'Arial' }, + '20pt Arial', + { size: 26.666666666666668, unit: 'pt', family: 'Arial' }, + '20.5pt Arial', + { size: 27.333333333333332, unit: 'pt', family: 'Arial' }, + '20% Arial', + { size: 20, unit: '%', family: 'Arial' }, // TODO I think this is a bad assertion - ZB 23-Jul-2017 + '20mm Arial', + { size: 75.59055118110237, unit: 'mm', family: 'Arial' }, + '20px serif', + { size: 20, unit: 'px', family: 'serif' }, + '20px sans-serif', + { size: 20, unit: 'px', family: 'sans-serif' }, + '20px monospace', + { size: 20, unit: 'px', family: 'monospace' }, + '50px Arial, sans-serif', + { size: 50, unit: 'px', family: 'Arial,sans-serif' }, + 'bold italic 50px Arial, sans-serif', + { style: 'italic', weight: 'bold', size: 50, unit: 'px', family: 'Arial,sans-serif' }, + '50px Helvetica , Arial, sans-serif', + { size: 50, unit: 'px', family: 'Helvetica,Arial,sans-serif' }, + '50px "Helvetica Neue", sans-serif', + { size: 50, unit: 'px', family: 'Helvetica Neue,sans-serif' }, + '50px "Helvetica Neue", "foo bar baz" , sans-serif', + { size: 50, unit: 'px', family: 'Helvetica Neue,foo bar baz,sans-serif' }, + "50px 'Helvetica Neue'", + { size: 50, unit: 'px', family: 'Helvetica Neue' }, + 'italic 20px Arial', + { size: 20, unit: 'px', style: 'italic', family: 'Arial' }, + 'oblique 20px Arial', + { size: 20, unit: 'px', style: 'oblique', family: 'Arial' }, + 'normal 20px Arial', + { size: 20, unit: 'px', style: 'normal', family: 'Arial' }, + '300 20px Arial', + { size: 20, unit: 'px', weight: '300', family: 'Arial' }, + '800 20px Arial', + { size: 20, unit: 'px', weight: '800', family: 'Arial' }, + 'bolder 20px Arial', + { size: 20, unit: 'px', weight: 'bolder', family: 'Arial' }, + 'lighter 20px Arial', + { size: 20, unit: 'px', weight: 'lighter', family: 'Arial' }, + 'normal normal normal 16px Impact', + { size: 16, unit: 'px', weight: 'normal', family: 'Impact', style: 'normal', variant: 'normal' }, + 'italic small-caps bolder 16px cursive', + { size: 16, unit: 'px', style: 'italic', variant: 'small-caps', weight: 'bolder', family: 'cursive' }, + '20px "new century schoolbook", serif', + { size: 20, unit: 'px', family: 'new century schoolbook,serif' }, + '20px "Arial bold 300"', // synthetic case with weight keyword inside family + { size: 20, unit: 'px', family: 'Arial bold 300', variant: 'normal' } + ] for (var i = 0, len = tests.length; i < len; ++i) { var str = tests[i++] - , expected = tests[i] - , actual = parseFont(str); - if (!expected.style) expected.style = 'normal'; - if (!expected.weight) expected.weight = 'normal'; - if (!expected.stretch) expected.stretch = 'normal'; - if (!expected.variant) expected.variant = 'normal'; + var expected = tests[i] + + var actual = parseFont(str) - assert.deepEqual(actual, expected, 'Failed to parse: ' + str); + if (!expected.style) expected.style = 'normal' + if (!expected.weight) expected.weight = 'normal' + if (!expected.stretch) expected.stretch = 'normal' + if (!expected.variant) expected.variant = 'normal' + + assert.deepEqual(actual, expected, 'Failed to parse: ' + str) } assert.strictEqual(parseFont('Helvetica, sans'), undefined) @@ -101,58 +103,60 @@ describe('Canvas', function () { it('registerFont', function () { // Minimal test to make sure nothing is thrown - registerFont('./examples/pfennigFont/Pfennig.ttf', {family: 'Pfennig'}) - registerFont('./examples/pfennigFont/PfennigBold.ttf', {family: 'Pfennig', weight: 'bold'}) - }); + registerFont('./examples/pfennigFont/Pfennig.ttf', { family: 'Pfennig' }) + registerFont('./examples/pfennigFont/PfennigBold.ttf', { family: 'Pfennig', weight: 'bold' }) + }) it('color serialization', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - ['fillStyle', 'strokeStyle', 'shadowColor'].forEach(function(prop){ - ctx[prop] = '#FFFFFF'; - assert.equal('#ffffff', ctx[prop], prop + ' #FFFFFF -> #ffffff, got ' + ctx[prop]); + var ctx = canvas.getContext('2d'); - ctx[prop] = '#FFF'; - assert.equal('#ffffff', ctx[prop], prop + ' #FFF -> #ffffff, got ' + ctx[prop]); + ['fillStyle', 'strokeStyle', 'shadowColor'].forEach(function (prop) { + ctx[prop] = '#FFFFFF' + assert.equal('#ffffff', ctx[prop], prop + ' #FFFFFF -> #ffffff, got ' + ctx[prop]) - ctx[prop] = 'rgba(128, 200, 128, 1)'; - assert.equal('#80c880', ctx[prop], prop + ' rgba(128, 200, 128, 1) -> #80c880, got ' + ctx[prop]); + ctx[prop] = '#FFF' + assert.equal('#ffffff', ctx[prop], prop + ' #FFF -> #ffffff, got ' + ctx[prop]) - ctx[prop] = 'rgba(128,80,0,0.5)'; - assert.equal('rgba(128, 80, 0, 0.50)', ctx[prop], prop + ' rgba(128,80,0,0.5) -> rgba(128, 80, 0, 0.5), got ' + ctx[prop]); + ctx[prop] = 'rgba(128, 200, 128, 1)' + assert.equal('#80c880', ctx[prop], prop + ' rgba(128, 200, 128, 1) -> #80c880, got ' + ctx[prop]) - ctx[prop] = 'rgba(128,80,0,0.75)'; - assert.equal('rgba(128, 80, 0, 0.75)', ctx[prop], prop + ' rgba(128,80,0,0.75) -> rgba(128, 80, 0, 0.75), got ' + ctx[prop]); + ctx[prop] = 'rgba(128,80,0,0.5)' + assert.equal('rgba(128, 80, 0, 0.50)', ctx[prop], prop + ' rgba(128,80,0,0.5) -> rgba(128, 80, 0, 0.5), got ' + ctx[prop]) - if ('shadowColor' == prop) return; + ctx[prop] = 'rgba(128,80,0,0.75)' + assert.equal('rgba(128, 80, 0, 0.75)', ctx[prop], prop + ' rgba(128,80,0,0.75) -> rgba(128, 80, 0, 0.75), got ' + ctx[prop]) - var grad = ctx.createLinearGradient(0,0,0,150); - ctx[prop] = grad; - assert.strictEqual(grad, ctx[prop], prop + ' pattern getter failed'); - }); - }); + if (prop == 'shadowColor') return + + var grad = ctx.createLinearGradient(0, 0, 0, 150) + ctx[prop] = grad + assert.strictEqual(grad, ctx[prop], prop + ' pattern getter failed') + }) + }) it('color parser', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - ctx.fillStyle = '#ffccaa'; - assert.equal('#ffccaa', ctx.fillStyle); + var ctx = canvas.getContext('2d') - ctx.fillStyle = '#FFCCAA'; - assert.equal('#ffccaa', ctx.fillStyle); + ctx.fillStyle = '#ffccaa' + assert.equal('#ffccaa', ctx.fillStyle) - ctx.fillStyle = '#FCA'; - assert.equal('#ffccaa', ctx.fillStyle); + ctx.fillStyle = '#FFCCAA' + assert.equal('#ffccaa', ctx.fillStyle) - ctx.fillStyle = '#fff'; - ctx.fillStyle = '#FGG'; - assert.equal('#ff0000', ctx.fillStyle); + ctx.fillStyle = '#FCA' + assert.equal('#ffccaa', ctx.fillStyle) - ctx.fillStyle = '#fff'; - ctx.fillStyle = 'afasdfasdf'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = '#fff' + ctx.fillStyle = '#FGG' + assert.equal('#ff0000', ctx.fillStyle) + + ctx.fillStyle = '#fff' + ctx.fillStyle = 'afasdfasdf' + assert.equal('#ffffff', ctx.fillStyle) // #rgba and #rrggbbaa ctx.fillStyle = '#ffccaa80' @@ -164,108 +168,109 @@ describe('Canvas', function () { ctx.fillStyle = '#BEAD' assert.equal('rgba(187, 238, 170, 0.87)', ctx.fillStyle) - ctx.fillStyle = 'rgb(255,255,255)'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = 'rgb(255,255,255)' + assert.equal('#ffffff', ctx.fillStyle) - ctx.fillStyle = 'rgb(0,0,0)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'rgb(0,0,0)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'rgb( 0 , 0 , 0)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'rgb( 0 , 0 , 0)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'rgba( 0 , 0 , 0, 1)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'rgba( 0 , 0 , 0, 1)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'rgba( 255, 200, 90, 0.5)'; - assert.equal('rgba(255, 200, 90, 0.50)', ctx.fillStyle); + ctx.fillStyle = 'rgba( 255, 200, 90, 0.5)' + assert.equal('rgba(255, 200, 90, 0.50)', ctx.fillStyle) - ctx.fillStyle = 'rgba( 255, 200, 90, 0.75)'; - assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle); + ctx.fillStyle = 'rgba( 255, 200, 90, 0.75)' + assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle) - ctx.fillStyle = 'rgba( 255, 200, 90, 0.7555)'; - assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle); + ctx.fillStyle = 'rgba( 255, 200, 90, 0.7555)' + assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle) - ctx.fillStyle = 'rgba( 255, 200, 90, .7555)'; - assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle); + ctx.fillStyle = 'rgba( 255, 200, 90, .7555)' + assert.equal('rgba(255, 200, 90, 0.75)', ctx.fillStyle) - ctx.fillStyle = 'rgb(0, 0, 9000)'; - assert.equal('#0000ff', ctx.fillStyle); + ctx.fillStyle = 'rgb(0, 0, 9000)' + assert.equal('#0000ff', ctx.fillStyle) - ctx.fillStyle = 'rgba(0, 0, 0, 42.42)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'rgba(0, 0, 0, 42.42)' + assert.equal('#000000', ctx.fillStyle) // hsl / hsla tests - ctx.fillStyle = 'hsl(0, 0%, 0%)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'hsl(0, 0%, 0%)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'hsl(3600, -10%, -10%)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'hsl(3600, -10%, -10%)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'hsl(10, 100%, 42%)'; - assert.equal('#d62400', ctx.fillStyle); + ctx.fillStyle = 'hsl(10, 100%, 42%)' + assert.equal('#d62400', ctx.fillStyle) - ctx.fillStyle = 'hsl(370, 120%, 42%)'; - assert.equal('#d62400', ctx.fillStyle); + ctx.fillStyle = 'hsl(370, 120%, 42%)' + assert.equal('#d62400', ctx.fillStyle) - ctx.fillStyle = 'hsl(0, 100%, 100%)'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = 'hsl(0, 100%, 100%)' + assert.equal('#ffffff', ctx.fillStyle) - ctx.fillStyle = 'hsl(0, 150%, 150%)'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = 'hsl(0, 150%, 150%)' + assert.equal('#ffffff', ctx.fillStyle) - ctx.fillStyle = 'hsl(237, 76%, 25%)'; - assert.equal('#0f1470', ctx.fillStyle); + ctx.fillStyle = 'hsl(237, 76%, 25%)' + assert.equal('#0f1470', ctx.fillStyle) - ctx.fillStyle = 'hsl(240, 73%, 25%)'; - assert.equal('#11116e', ctx.fillStyle); + ctx.fillStyle = 'hsl(240, 73%, 25%)' + assert.equal('#11116e', ctx.fillStyle) - ctx.fillStyle = 'hsl(262, 32%, 42%)'; - assert.equal('#62498d', ctx.fillStyle); + ctx.fillStyle = 'hsl(262, 32%, 42%)' + assert.equal('#62498d', ctx.fillStyle) - ctx.fillStyle = 'hsla(0, 0%, 0%, 1)'; - assert.equal('#000000', ctx.fillStyle); + ctx.fillStyle = 'hsla(0, 0%, 0%, 1)' + assert.equal('#000000', ctx.fillStyle) - ctx.fillStyle = 'hsla(0, 100%, 100%, 1)'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = 'hsla(0, 100%, 100%, 1)' + assert.equal('#ffffff', ctx.fillStyle) - ctx.fillStyle = 'hsla(120, 25%, 75%, 0.5)'; - assert.equal('rgba(175, 207, 175, 0.50)', ctx.fillStyle); + ctx.fillStyle = 'hsla(120, 25%, 75%, 0.5)' + assert.equal('rgba(175, 207, 175, 0.50)', ctx.fillStyle) - ctx.fillStyle = 'hsla(240, 75%, 25%, 0.75)'; - assert.equal('rgba(16, 16, 112, 0.75)', ctx.fillStyle); + ctx.fillStyle = 'hsla(240, 75%, 25%, 0.75)' + assert.equal('rgba(16, 16, 112, 0.75)', ctx.fillStyle) - ctx.fillStyle = 'hsla(172.0, 33.00000e0%, 42%, 1)'; - assert.equal('#488e85', ctx.fillStyle); + ctx.fillStyle = 'hsla(172.0, 33.00000e0%, 42%, 1)' + assert.equal('#488e85', ctx.fillStyle) - ctx.fillStyle = 'hsl(124.5, 76.1%, 47.6%)'; - assert.equal('#1dd62b', ctx.fillStyle); + ctx.fillStyle = 'hsl(124.5, 76.1%, 47.6%)' + assert.equal('#1dd62b', ctx.fillStyle) - ctx.fillStyle = 'hsl(1.24e2, 760e-1%, 4.7e1%)'; - assert.equal('#1dd329', ctx.fillStyle); + ctx.fillStyle = 'hsl(1.24e2, 760e-1%, 4.7e1%)' + assert.equal('#1dd329', ctx.fillStyle) // case-insensitive (#235) - ctx.fillStyle = "sILveR"; - assert.equal(ctx.fillStyle, "#c0c0c0"); - }); + ctx.fillStyle = 'sILveR' + assert.equal(ctx.fillStyle, '#c0c0c0') + }) it('Canvas#type', function () { - var canvas = createCanvas(10, 10); - assert.equal(canvas.type, 'image'); - var canvas = createCanvas(10, 10, 'pdf'); - assert.equal(canvas.type, 'pdf'); - var canvas = createCanvas(10, 10, 'svg'); - assert.equal(canvas.type, 'svg'); - var canvas = createCanvas(10, 10, 'hey'); - assert.equal(canvas.type, 'image'); - }); + var canvas = createCanvas(10, 10) + assert.equal(canvas.type, 'image') + var canvas = createCanvas(10, 10, 'pdf') + assert.equal(canvas.type, 'pdf') + var canvas = createCanvas(10, 10, 'svg') + assert.equal(canvas.type, 'svg') + + assert.throws(function () { createCanvas(10, 10, 'hey'); }, /RangeError/); + }) it('Canvas#getContext("2d")', function () { var canvas = createCanvas(200, 300) - , ctx = canvas.getContext('2d'); - assert.ok('object' == typeof ctx); - assert.equal(canvas, ctx.canvas, 'context.canvas is not canvas'); - assert.equal(ctx, canvas.context, 'canvas.context is not context'); + + var ctx = canvas.getContext('2d') + assert.ok(typeof ctx === 'object') + assert.equal(canvas, ctx.canvas, 'context.canvas is not canvas') + assert.equal(ctx, canvas.context, 'canvas.context is not context') const MAX_IMAGE_SIZE = 32767; @@ -280,77 +285,76 @@ describe('Canvas', function () { [Math.pow(2, 30), 0, 3], [Math.pow(2, 30), 1, 3], [Math.pow(2, 32), 0, 1], - [Math.pow(2, 32), 1, 1], + [Math.pow(2, 32), 1, 1] ].forEach(params => { - var width = params[0]; - var height = params[1]; - var errorLevel = params[2]; + var width = params[0] + var height = params[1] + var errorLevel = params[2] - var level = 3; + var level = 3 try { - var canvas = createCanvas(width, height); - level--; + var canvas = createCanvas(width, height) + level-- - var ctx = canvas.getContext('2d'); - level--; + var ctx = canvas.getContext('2d') + level-- - ctx.getImageData(0, 0, 1, 1); - level--; + ctx.getImageData(0, 0, 1, 1) + level-- } catch (err) {} - if (errorLevel !== null) - assert.strictEqual(level, errorLevel); - }); - }); + if (errorLevel !== null) { assert.strictEqual(level, errorLevel) } + }) + }) it('Canvas#getContext("2d", {pixelFormat: string})', function () { - var canvas, context; + var canvas, context // default: - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "RGBA32"}); - assert.equal(context.pixelFormat, "RGBA32"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'RGBA32' }) + assert.equal(context.pixelFormat, 'RGBA32') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "RGBA32"}); - assert.equal(context.pixelFormat, "RGBA32"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'RGBA32' }) + assert.equal(context.pixelFormat, 'RGBA32') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "RGB24"}); - assert.equal(context.pixelFormat, "RGB24"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'RGB24' }) + assert.equal(context.pixelFormat, 'RGB24') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "A8"}); - assert.equal(context.pixelFormat, "A8"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'A8' }) + assert.equal(context.pixelFormat, 'A8') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "A1"}); - assert.equal(context.pixelFormat, "A1"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'A1' }) + assert.equal(context.pixelFormat, 'A1') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "RGB16_565"}); - assert.equal(context.pixelFormat, "RGB16_565"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'RGB16_565' }) + assert.equal(context.pixelFormat, 'RGB16_565') // Not tested: RGB30 - }); + }) it('Canvas#getContext("2d", {alpha: boolean})', function () { - var canvas, context; + var canvas, context - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {alpha: true}); - assert.equal(context.pixelFormat, "RGBA32"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { alpha: true }) + assert.equal(context.pixelFormat, 'RGBA32') - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {alpha: false}); - assert.equal(context.pixelFormat, "RGB24"); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { alpha: false }) + assert.equal(context.pixelFormat, 'RGB24') // alpha takes priority: - canvas = createCanvas(10, 10); - context = canvas.getContext("2d", {pixelFormat: "RGBA32", alpha: false}); - assert.equal(context.pixelFormat, "RGB24"); - }); + canvas = createCanvas(10, 10) + context = canvas.getContext('2d', { pixelFormat: 'RGBA32', alpha: false }) + assert.equal(context.pixelFormat, 'RGB24') + }) it('Canvas#{width,height}=', function () { const canvas = createCanvas(100, 200); @@ -406,33 +410,35 @@ describe('Canvas', function () { assert.ok(canvas.stride < 1024, 'canvas.stride seems too long'); // TODO test stride on other formats - }); + }) it('Canvas#getContext("invalid")', function () { - assert.equal(null, createCanvas(200, 300).getContext('invalid')); - }); + assert.equal(null, createCanvas(200, 300).getContext('invalid')) + }) it('Context2d#patternQuality', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal('good', ctx.patternQuality); - ctx.patternQuality = 'best'; - assert.equal('best', ctx.patternQuality); - ctx.patternQuality = 'invalid'; - assert.equal('best', ctx.patternQuality); - }); + var ctx = canvas.getContext('2d') + + assert.equal('good', ctx.patternQuality) + ctx.patternQuality = 'best' + assert.equal('best', ctx.patternQuality) + ctx.patternQuality = 'invalid' + assert.equal('best', ctx.patternQuality) + }) it('Context2d#imageSmoothingEnabled', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal(true, ctx.imageSmoothingEnabled); - ctx.imageSmoothingEnabled = false; - assert.equal('good', ctx.patternQuality); - assert.equal(false, ctx.imageSmoothingEnabled); - assert.equal('good', ctx.patternQuality); - }); + var ctx = canvas.getContext('2d') + + assert.equal(true, ctx.imageSmoothingEnabled) + ctx.imageSmoothingEnabled = false + assert.equal('good', ctx.patternQuality) + assert.equal(false, ctx.imageSmoothingEnabled) + assert.equal('good', ctx.patternQuality) + }) it('Context2d#font=', function () { const canvas = createCanvas(200, 200) @@ -448,111 +454,118 @@ describe('Canvas', function () { it('Context2d#lineWidth=', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - - ctx.lineWidth = 10.0; - assert.equal(10, ctx.lineWidth); - ctx.lineWidth = Infinity; - assert.equal(10, ctx.lineWidth); - ctx.lineWidth = -Infinity; - assert.equal(10, ctx.lineWidth); - ctx.lineWidth = -5; - assert.equal(10, ctx.lineWidth); - ctx.lineWidth = 0; - assert.equal(10, ctx.lineWidth); - }); + + var ctx = canvas.getContext('2d') + + ctx.lineWidth = 10.0 + assert.equal(10, ctx.lineWidth) + ctx.lineWidth = Infinity + assert.equal(10, ctx.lineWidth) + ctx.lineWidth = -Infinity + assert.equal(10, ctx.lineWidth) + ctx.lineWidth = -5 + assert.equal(10, ctx.lineWidth) + ctx.lineWidth = 0 + assert.equal(10, ctx.lineWidth) + }) it('Context2d#antiAlias=', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - - assert.equal('default', ctx.antialias); - ctx.antialias = 'none'; - assert.equal('none', ctx.antialias); - ctx.antialias = 'gray'; - assert.equal('gray', ctx.antialias); - ctx.antialias = 'subpixel'; - assert.equal('subpixel', ctx.antialias); - ctx.antialias = 'invalid'; - assert.equal('subpixel', ctx.antialias); - ctx.antialias = 1; - assert.equal('subpixel', ctx.antialias); - }); + + var ctx = canvas.getContext('2d') + + assert.equal('default', ctx.antialias) + ctx.antialias = 'none' + assert.equal('none', ctx.antialias) + ctx.antialias = 'gray' + assert.equal('gray', ctx.antialias) + ctx.antialias = 'subpixel' + assert.equal('subpixel', ctx.antialias) + ctx.antialias = 'invalid' + assert.equal('subpixel', ctx.antialias) + ctx.antialias = 1 + assert.equal('subpixel', ctx.antialias) + }) it('Context2d#lineCap=', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal('butt', ctx.lineCap); - ctx.lineCap = 'round'; - assert.equal('round', ctx.lineCap); - }); + var ctx = canvas.getContext('2d') + + assert.equal('butt', ctx.lineCap) + ctx.lineCap = 'round' + assert.equal('round', ctx.lineCap) + }) it('Context2d#lineJoin=', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal('miter', ctx.lineJoin); - ctx.lineJoin = 'round'; - assert.equal('round', ctx.lineJoin); - }); + var ctx = canvas.getContext('2d') + + assert.equal('miter', ctx.lineJoin) + ctx.lineJoin = 'round' + assert.equal('round', ctx.lineJoin) + }) it('Context2d#globalAlpha=', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal(1, ctx.globalAlpha); + var ctx = canvas.getContext('2d') + + assert.equal(1, ctx.globalAlpha) ctx.globalAlpha = 0.5 - assert.equal(0.5, ctx.globalAlpha); - }); + assert.equal(0.5, ctx.globalAlpha) + }) it('Context2d#isPointInPath()', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - - ctx.rect(5,5,100,100); - ctx.rect(50,100,10,10); - assert.ok(ctx.isPointInPath(10,10)); - assert.ok(ctx.isPointInPath(10,50)); - assert.ok(ctx.isPointInPath(100,100)); - assert.ok(ctx.isPointInPath(105,105)); - assert.ok(!ctx.isPointInPath(106,105)); - assert.ok(!ctx.isPointInPath(150,150)); - - assert.ok(ctx.isPointInPath(50,110)); - assert.ok(ctx.isPointInPath(60,110)); - assert.ok(!ctx.isPointInPath(70,110)); - assert.ok(!ctx.isPointInPath(50,120)); - }); + + var ctx = canvas.getContext('2d') + + ctx.rect(5, 5, 100, 100) + ctx.rect(50, 100, 10, 10) + assert.ok(ctx.isPointInPath(10, 10)) + assert.ok(ctx.isPointInPath(10, 50)) + assert.ok(ctx.isPointInPath(100, 100)) + assert.ok(ctx.isPointInPath(105, 105)) + assert.ok(!ctx.isPointInPath(106, 105)) + assert.ok(!ctx.isPointInPath(150, 150)) + + assert.ok(ctx.isPointInPath(50, 110)) + assert.ok(ctx.isPointInPath(60, 110)) + assert.ok(!ctx.isPointInPath(70, 110)) + assert.ok(!ctx.isPointInPath(50, 120)) + }) it('Context2d#textAlign', function () { - var canvas = createCanvas(200,200) - , ctx = canvas.getContext('2d'); - - assert.equal('start', ctx.textAlign); - ctx.textAlign = 'center'; - assert.equal('center', ctx.textAlign); - ctx.textAlign = 'right'; - assert.equal('right', ctx.textAlign); - ctx.textAlign = 'end'; - assert.equal('end', ctx.textAlign); - ctx.textAlign = 'fail'; - assert.equal('end', ctx.textAlign); - }); + var canvas = createCanvas(200, 200) + + var ctx = canvas.getContext('2d') + + assert.equal('start', ctx.textAlign) + ctx.textAlign = 'center' + assert.equal('center', ctx.textAlign) + ctx.textAlign = 'right' + assert.equal('right', ctx.textAlign) + ctx.textAlign = 'end' + assert.equal('end', ctx.textAlign) + ctx.textAlign = 'fail' + assert.equal('end', ctx.textAlign) + }) describe('#toBuffer', function () { it('Canvas#toBuffer()', function () { - var buf = createCanvas(200,200).toBuffer(); - assert.equal('PNG', buf.slice(1,4).toString()); - }); + var buf = createCanvas(200, 200).toBuffer() + assert.equal('PNG', buf.slice(1, 4).toString()) + }) it('Canvas#toBuffer("image/png")', function () { - var buf = createCanvas(200,200).toBuffer('image/png'); - assert.equal('PNG', buf.slice(1,4).toString()); - }); + var buf = createCanvas(200, 200).toBuffer('image/png') + assert.equal('PNG', buf.slice(1, 4).toString()) + }) it('Canvas#toBuffer("image/png", {resolution: 96})', function () { - const buf = createCanvas(200, 200).toBuffer('image/png', {resolution: 96}); + const buf = createCanvas(200, 200).toBuffer('image/png', { resolution: 96 }) // 3780 ppm ~= 96 ppi let foundpHYs = false; for (let i = 0; i < buf.length - 12; i++) { @@ -575,73 +588,74 @@ describe('Canvas', function () { }) it('Canvas#toBuffer("image/png", {compressionLevel: 5})', function () { - var buf = createCanvas(200,200).toBuffer('image/png', {compressionLevel: 5}); - assert.equal('PNG', buf.slice(1,4).toString()); - }); + var buf = createCanvas(200, 200).toBuffer('image/png', { compressionLevel: 5 }) + assert.equal('PNG', buf.slice(1, 4).toString()) + }) it('Canvas#toBuffer("image/jpeg")', function () { - var buf = createCanvas(200,200).toBuffer('image/jpeg'); - assert.equal(buf[0], 0xff); - assert.equal(buf[1], 0xd8); - assert.equal(buf[buf.byteLength - 2], 0xff); - assert.equal(buf[buf.byteLength - 1], 0xd9); - }); + var buf = createCanvas(200, 200).toBuffer('image/jpeg') + assert.equal(buf[0], 0xff) + assert.equal(buf[1], 0xd8) + assert.equal(buf[buf.byteLength - 2], 0xff) + assert.equal(buf[buf.byteLength - 1], 0xd9) + }) it('Canvas#toBuffer("image/jpeg", {quality: 0.95})', function () { - var buf = createCanvas(200,200).toBuffer('image/jpeg', {quality: 0.95}); - assert.equal(buf[0], 0xff); - assert.equal(buf[1], 0xd8); - assert.equal(buf[buf.byteLength - 2], 0xff); - assert.equal(buf[buf.byteLength - 1], 0xd9); - }); + var buf = createCanvas(200, 200).toBuffer('image/jpeg', { quality: 0.95 }) + assert.equal(buf[0], 0xff) + assert.equal(buf[1], 0xd8) + assert.equal(buf[buf.byteLength - 2], 0xff) + assert.equal(buf[buf.byteLength - 1], 0xd9) + }) it('Canvas#toBuffer(callback)', function (done) { - createCanvas(200, 200).toBuffer(function(err, buf){ - assert.ok(!err); - assert.equal('PNG', buf.slice(1,4).toString()); - done(); - }); - }); + createCanvas(200, 200).toBuffer(function (err, buf) { + assert.ok(!err) + assert.equal('PNG', buf.slice(1, 4).toString()) + done() + }) + }) it('Canvas#toBuffer(callback, "image/jpeg")', function (done) { - createCanvas(200,200).toBuffer(function (err, buf) { - assert.ok(!err); - assert.equal(buf[0], 0xff); - assert.equal(buf[1], 0xd8); - assert.equal(buf[buf.byteLength - 2], 0xff); - assert.equal(buf[buf.byteLength - 1], 0xd9); - done(); - }, 'image/jpeg'); - }); + createCanvas(200, 200).toBuffer(function (err, buf) { + assert.ok(!err) + assert.equal(buf[0], 0xff) + assert.equal(buf[1], 0xd8) + assert.equal(buf[buf.byteLength - 2], 0xff) + assert.equal(buf[buf.byteLength - 1], 0xd9) + done() + }, 'image/jpeg') + }) it('Canvas#toBuffer(callback, "image/jpeg", {quality: 0.95})', function (done) { - createCanvas(200,200).toBuffer(function (err, buf) { - assert.ok(!err); - assert.equal(buf[0], 0xff); - assert.equal(buf[1], 0xd8); - assert.equal(buf[buf.byteLength - 2], 0xff); - assert.equal(buf[buf.byteLength - 1], 0xd9); - done(); - }, 'image/jpeg', {quality: 0.95}); - }); + createCanvas(200, 200).toBuffer(function (err, buf) { + assert.ok(!err) + assert.equal(buf[0], 0xff) + assert.equal(buf[1], 0xd8) + assert.equal(buf[buf.byteLength - 2], 0xff) + assert.equal(buf[buf.byteLength - 1], 0xd9) + done() + }, 'image/jpeg', { quality: 0.95 }) + }) - describe('#toBuffer("raw")', function() { + describe('#toBuffer("raw")', function () { var canvas = createCanvas(11, 10) - , ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, 11, 10); + var ctx = canvas.getContext('2d') + + ctx.clearRect(0, 0, 11, 10) - ctx.fillStyle = 'rgba(200, 200, 200, 0.505)'; - ctx.fillRect(0, 0, 5, 5); + ctx.fillStyle = 'rgba(200, 200, 200, 0.505)' + ctx.fillRect(0, 0, 5, 5) - ctx.fillStyle = 'red'; - ctx.fillRect(5, 0, 5, 5); + ctx.fillStyle = 'red' + ctx.fillRect(5, 0, 5, 5) - ctx.fillStyle = '#00ff00'; - ctx.fillRect(0, 5, 5, 5); + ctx.fillStyle = '#00ff00' + ctx.fillRect(0, 5, 5, 5) - ctx.fillStyle = 'black'; - ctx.fillRect(5, 5, 4, 5); + ctx.fillStyle = 'black' + ctx.fillRect(5, 5, 4, 5) /** Output: * *****RRRRR- @@ -656,284 +670,291 @@ describe('Canvas', function () { * GGGGGBBBB-- */ - var buf = canvas.toBuffer('raw'); - var stride = canvas.stride; + var buf = canvas.toBuffer('raw') + var stride = canvas.stride - var endianness = os.endianness(); + var endianness = os.endianness() - function assertPixel(u32, x, y, message) { - var expected = '0x' + u32.toString(16); + function assertPixel (u32, x, y, message) { + var expected = '0x' + u32.toString(16) // Buffer doesn't have readUInt32(): it only has readUInt32LE() and // readUInt32BE(). - var px = buf['readUInt32' + endianness](y * stride + x * 4); - var actual = '0x' + px.toString(16); + var px = buf['readUInt32' + endianness](y * stride + x * 4) + var actual = '0x' + px.toString(16) - assert.equal(actual, expected, message); + assert.equal(actual, expected, message) } - it('should have the correct size', function() { - assert.equal(buf.length, stride * 10); - }); - - it('does not premultiply alpha', function() { - assertPixel(0x80646464, 0, 0, 'first semitransparent pixel'); - assertPixel(0x80646464, 4, 4, 'last semitransparent pixel'); - }); - - it('draws red', function() { - assertPixel(0xffff0000, 5, 0, 'first red pixel'); - assertPixel(0xffff0000, 9, 4, 'last red pixel'); - }); - - it('draws green', function() { - assertPixel(0xff00ff00, 0, 5, 'first green pixel'); - assertPixel(0xff00ff00, 4, 9, 'last green pixel'); - }); - - it('draws black', function() { - assertPixel(0xff000000, 5, 5, 'first black pixel'); - assertPixel(0xff000000, 8, 9, 'last black pixel'); - }); - - it('leaves undrawn pixels black, transparent', function() { - assertPixel(0x0, 9, 5, 'first undrawn pixel'); - assertPixel(0x0, 9, 9, 'last undrawn pixel'); - }); - - it('is immutable', function() { - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, 10, 10); - canvas.toBuffer('raw'); // (side-effect: flushes canvas) - assertPixel(0xffff0000, 5, 0, 'first red pixel'); - }); - }); - }); + it('should have the correct size', function () { + assert.equal(buf.length, stride * 10) + }) + + it('does not premultiply alpha', function () { + assertPixel(0x80646464, 0, 0, 'first semitransparent pixel') + assertPixel(0x80646464, 4, 4, 'last semitransparent pixel') + }) + + it('draws red', function () { + assertPixel(0xffff0000, 5, 0, 'first red pixel') + assertPixel(0xffff0000, 9, 4, 'last red pixel') + }) + + it('draws green', function () { + assertPixel(0xff00ff00, 0, 5, 'first green pixel') + assertPixel(0xff00ff00, 4, 9, 'last green pixel') + }) + + it('draws black', function () { + assertPixel(0xff000000, 5, 5, 'first black pixel') + assertPixel(0xff000000, 8, 9, 'last black pixel') + }) + + it('leaves undrawn pixels black, transparent', function () { + assertPixel(0x0, 9, 5, 'first undrawn pixel') + assertPixel(0x0, 9, 9, 'last undrawn pixel') + }) + + it('is immutable', function () { + ctx.fillStyle = 'white' + ctx.fillRect(0, 0, 10, 10) + canvas.toBuffer('raw') // (side-effect: flushes canvas) + assertPixel(0xffff0000, 5, 0, 'first red pixel') + }) + }) + }) describe('#toDataURL()', function () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - ctx.fillRect(0,0,100,100); - ctx.fillStyle = 'red'; - ctx.fillRect(100,0,100,100); + var ctx = canvas.getContext('2d') + + ctx.fillRect(0, 0, 100, 100) + ctx.fillStyle = 'red' + ctx.fillRect(100, 0, 100, 100) it('toDataURL() works and defaults to PNG', function () { - assert.ok(canvas.toDataURL().startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL().startsWith('data:image/png;base64,')) + }) it('toDataURL(0.5) works and defaults to PNG', function () { - assert.ok(canvas.toDataURL(0.5).startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL(0.5).startsWith('data:image/png;base64,')) + }) it('toDataURL(undefined) works and defaults to PNG', function () { - assert.ok(canvas.toDataURL(undefined).startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL(undefined).startsWith('data:image/png;base64,')) + }) it('toDataURL("image/png") works', function () { - assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')) + }) it('toDataURL("image/png", 0.5) works', function () { - assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL('image/png').startsWith('data:image/png;base64,')) + }) it('toDataURL("iMaGe/PNg") works', function () { - assert.ok(canvas.toDataURL('iMaGe/PNg').startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL('iMaGe/PNg').startsWith('data:image/png;base64,')) + }) it('toDataURL("image/jpeg") works', function () { - assert.ok(canvas.toDataURL('image/jpeg').startsWith('data:image/jpeg;base64,')); - }); + assert.ok(canvas.toDataURL('image/jpeg').startsWith('data:image/jpeg;base64,')) + }) it('toDataURL(function (err, str) {...}) works and defaults to PNG', function (done) { - createCanvas(200,200).toDataURL(function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/png;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL(function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/png;base64,') === 0) + done() + }) + }) it('toDataURL(function (err, str) {...}) is async even with no canvas data', function (done) { - createCanvas().toDataURL(function(err, str){ - assert.ifError(err); - assert.ok('data:,' === str); - done(); - }); - }); + createCanvas().toDataURL(function (err, str) { + assert.ifError(err) + assert.ok(str === 'data:,') + done() + }) + }) it('toDataURL(0.5, function (err, str) {...}) works and defaults to PNG', function (done) { - createCanvas(200,200).toDataURL(0.5, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/png;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL(0.5, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/png;base64,') === 0) + done() + }) + }) it('toDataURL(undefined, function (err, str) {...}) works and defaults to PNG', function (done) { - createCanvas(200,200).toDataURL(undefined, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/png;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL(undefined, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/png;base64,') === 0) + done() + }) + }) it('toDataURL("image/png", function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/png', function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/png;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('image/png', function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/png;base64,') === 0) + done() + }) + }) it('toDataURL("image/png", 0.5, function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/png', 0.5, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/png;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('image/png', 0.5, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/png;base64,') === 0) + done() + }) + }) it('toDataURL("image/png", {}) works', function () { - assert.ok(canvas.toDataURL('image/png', {}).startsWith('data:image/png;base64,')); - }); + assert.ok(canvas.toDataURL('image/png', {}).startsWith('data:image/png;base64,')) + }) it('toDataURL("image/jpeg", {}) works', function () { - assert.ok(canvas.toDataURL('image/jpeg', {}).startsWith('data:image/jpeg;base64,')); - }); + assert.ok(canvas.toDataURL('image/jpeg', {}).startsWith('data:image/jpeg;base64,')) + }) it('toDataURL("image/jpeg", function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/jpeg', function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/jpeg;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('image/jpeg', function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/jpeg;base64,') === 0) + done() + }) + }) it('toDataURL("iMAge/JPEG", function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('iMAge/JPEG', function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/jpeg;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('iMAge/JPEG', function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/jpeg;base64,') === 0) + done() + }) + }) it('toDataURL("image/jpeg", undefined, function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/jpeg', undefined, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/jpeg;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('image/jpeg', undefined, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/jpeg;base64,') === 0) + done() + }) + }) it('toDataURL("image/jpeg", 0.5, function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/jpeg', 0.5, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/jpeg;base64,')); - done(); - }); - }); + createCanvas(200, 200).toDataURL('image/jpeg', 0.5, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/jpeg;base64,') === 0) + done() + }) + }) it('toDataURL("image/jpeg", opts, function (err, str) {...}) works', function (done) { - createCanvas(200,200).toDataURL('image/jpeg', {quality: 100}, function(err, str){ - assert.ifError(err); - assert.ok(0 === str.indexOf('data:image/jpeg;base64,')); - done(); - }); - }); - }); + createCanvas(200, 200).toDataURL('image/jpeg', { quality: 100 }, function (err, str) { + assert.ifError(err) + assert.ok(str.indexOf('data:image/jpeg;base64,') === 0) + done() + }) + }) + }) describe('Context2d#createImageData(width, height)', function () { - it("works", function () { + it('works', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d'); - var imageData = ctx.createImageData(2,6); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(2 * 6 * 4, imageData.data.length); + var ctx = canvas.getContext('2d') - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - assert.equal(0, imageData.data[2]); - assert.equal(0, imageData.data[3]); - }); + var imageData = ctx.createImageData(2, 6) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(2 * 6 * 4, imageData.data.length) - it("works, A8 format", function () { + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + assert.equal(0, imageData.data[2]) + assert.equal(0, imageData.data[3]) + }) + + it('works, A8 format', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d', {pixelFormat: "A8"}); - var imageData = ctx.createImageData(2,6); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(2 * 6 * 1, imageData.data.length); + var ctx = canvas.getContext('2d', { pixelFormat: 'A8' }) - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - assert.equal(0, imageData.data[2]); - assert.equal(0, imageData.data[3]); - }); + var imageData = ctx.createImageData(2, 6) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(2 * 6 * 1, imageData.data.length) + + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + assert.equal(0, imageData.data[2]) + assert.equal(0, imageData.data[3]) + }) - it("works, A1 format", function () { + it('works, A1 format', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d', {pixelFormat: "A1"}); - var imageData = ctx.createImageData(2,6); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(Math.ceil(2 * 6 / 8), imageData.data.length); + var ctx = canvas.getContext('2d', { pixelFormat: 'A1' }) - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - }); + var imageData = ctx.createImageData(2, 6) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(Math.ceil(2 * 6 / 8), imageData.data.length) + + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + }) - it("works, RGB24 format", function () { + it('works, RGB24 format', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d', {pixelFormat: "RGB24"}); - var imageData = ctx.createImageData(2,6); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(2 * 6 * 4, imageData.data.length); + var ctx = canvas.getContext('2d', { pixelFormat: 'RGB24' }) - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - assert.equal(0, imageData.data[2]); - assert.equal(0, imageData.data[3]); - }); + var imageData = ctx.createImageData(2, 6) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(2 * 6 * 4, imageData.data.length) + + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + assert.equal(0, imageData.data[2]) + assert.equal(0, imageData.data[3]) + }) - it("works, RGB16_565 format", function () { + it('works, RGB16_565 format', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d', {pixelFormat: "RGB16_565"}); - var imageData = ctx.createImageData(2,6); - assert(imageData.data instanceof Uint16Array); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(2 * 6, imageData.data.length); + var ctx = canvas.getContext('2d', { pixelFormat: 'RGB16_565' }) - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - }); - }); + var imageData = ctx.createImageData(2, 6) + assert(imageData.data instanceof Uint16Array) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(2 * 6, imageData.data.length) + + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + }) + }) describe('Context2d#measureText()', function () { it('Context2d#measureText().width', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d'); - assert.ok(ctx.measureText('foo').width); - assert.ok(ctx.measureText('foo').width != ctx.measureText('foobar').width); - assert.ok(ctx.measureText('foo').width != ctx.measureText(' foo').width); - }); + var ctx = canvas.getContext('2d') + + assert.ok(ctx.measureText('foo').width) + assert.ok(ctx.measureText('foo').width != ctx.measureText('foobar').width) + assert.ok(ctx.measureText('foo').width != ctx.measureText(' foo').width) + }) it('works', function () { var canvas = createCanvas(20, 20) var ctx = canvas.getContext('2d') - ctx.font = "20px Arial" + ctx.font = '20px Arial' - ctx.textBaseline = "alphabetic" - var metrics = ctx.measureText("Alphabet") + ctx.textBaseline = 'alphabetic' + var metrics = ctx.measureText('Alphabet') // Zero if the given baseline is the alphabetic baseline assert.equal(metrics.alphabeticBaseline, 0) // Positive = going up from the baseline @@ -948,8 +969,8 @@ describe('Canvas', function () { assert.ok(metrics.actualBoundingBoxAscent > 0) // On the baseline or slightly above assert.ok(metrics.actualBoundingBoxDescent <= 0) - }); - }); + }) + }) it('Context2d#fillText()', function () { [ @@ -994,29 +1015,30 @@ describe('Canvas', function () { it('Context2d#createImageData(ImageData)', function () { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d'); - var imageData = ctx.createImageData(ctx.createImageData(2, 6)); - assert.equal(2, imageData.width); - assert.equal(6, imageData.height); - assert.equal(2 * 6 * 4, imageData.data.length); - }); + var ctx = canvas.getContext('2d') + + var imageData = ctx.createImageData(ctx.createImageData(2, 6)) + assert.equal(2, imageData.width) + assert.equal(6, imageData.height) + assert.equal(2 * 6 * 4, imageData.data.length) + }) describe('Context2d#getImageData()', function () { - function createTestCanvas(useAlpha, attributes) { - var canvas = createCanvas(3, 6); - var ctx = canvas.getContext('2d', attributes); + function createTestCanvas (useAlpha, attributes) { + var canvas = createCanvas(3, 6) + var ctx = canvas.getContext('2d', attributes) - ctx.fillStyle = useAlpha ? 'rgba(255,0,0,0.25)' : '#f00'; - ctx.fillRect(0,0,1,6); + ctx.fillStyle = useAlpha ? 'rgba(255,0,0,0.25)' : '#f00' + ctx.fillRect(0, 0, 1, 6) - ctx.fillStyle = useAlpha ? 'rgba(0,255,0,0.5)' : '#0f0'; - ctx.fillRect(1,0,1,6); + ctx.fillStyle = useAlpha ? 'rgba(0,255,0,0.5)' : '#0f0' + ctx.fillRect(1, 0, 1, 6) - ctx.fillStyle = useAlpha ? 'rgba(0,0,255,0.75)' : '#00f'; - ctx.fillRect(2,0,1,6); + ctx.fillStyle = useAlpha ? 'rgba(0,0,255,0.75)' : '#00f' + ctx.fillRect(2, 0, 1, 6) - return ctx; + return ctx } it("works, full width, RGBA32", function () { @@ -1182,590 +1204,591 @@ describe('Canvas', function () { }); it('Context2d#createPattern(Canvas)', function () { - var pattern = createCanvas(2,2) - , checkers = pattern.getContext('2d'); + var pattern = createCanvas(2, 2) + + var checkers = pattern.getContext('2d') // white - checkers.fillStyle = '#fff'; - checkers.fillRect(0,0,2,2); + checkers.fillStyle = '#fff' + checkers.fillRect(0, 0, 2, 2) // black - checkers.fillStyle = '#000'; - checkers.fillRect(0,0,1,1); - checkers.fillRect(1,1,1,1); + checkers.fillStyle = '#000' + checkers.fillRect(0, 0, 1, 1) + checkers.fillRect(1, 1, 1, 1) - var imageData = checkers.getImageData(0,0,2,2); - assert.equal(2, imageData.width); - assert.equal(2, imageData.height); - assert.equal(16, imageData.data.length); + var imageData = checkers.getImageData(0, 0, 2, 2) + assert.equal(2, imageData.width) + assert.equal(2, imageData.height) + assert.equal(16, imageData.data.length) // (0,0) black - assert.equal(0, imageData.data[0]); - assert.equal(0, imageData.data[1]); - assert.equal(0, imageData.data[2]); - assert.equal(255, imageData.data[3]); + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + assert.equal(0, imageData.data[2]) + assert.equal(255, imageData.data[3]) // (1,0) white - assert.equal(255, imageData.data[4]); - assert.equal(255, imageData.data[5]); - assert.equal(255, imageData.data[6]); - assert.equal(255, imageData.data[7]); + assert.equal(255, imageData.data[4]) + assert.equal(255, imageData.data[5]) + assert.equal(255, imageData.data[6]) + assert.equal(255, imageData.data[7]) // (0,1) white - assert.equal(255, imageData.data[8]); - assert.equal(255, imageData.data[9]); - assert.equal(255, imageData.data[10]); - assert.equal(255, imageData.data[11]); + assert.equal(255, imageData.data[8]) + assert.equal(255, imageData.data[9]) + assert.equal(255, imageData.data[10]) + assert.equal(255, imageData.data[11]) // (1,1) black - assert.equal(0, imageData.data[12]); - assert.equal(0, imageData.data[13]); - assert.equal(0, imageData.data[14]); - assert.equal(255, imageData.data[15]); + assert.equal(0, imageData.data[12]) + assert.equal(0, imageData.data[13]) + assert.equal(0, imageData.data[14]) + assert.equal(255, imageData.data[15]) var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d') - , pattern = ctx.createPattern(pattern); - - ctx.fillStyle = pattern; - ctx.fillRect(0,0,20,20); - - var imageData = ctx.getImageData(0,0,20,20); - assert.equal(20, imageData.width); - assert.equal(20, imageData.height); - assert.equal(1600, imageData.data.length); - - var i=0, b = true; - while(i { var canvas = createCanvas(20, 20) - , ctx = canvas.getContext('2d') - , pattern = ctx.createPattern(img); - ctx.fillStyle = pattern; - ctx.fillRect(0,0,20,20); + var ctx = canvas.getContext('2d') + + var pattern = ctx.createPattern(img) + + ctx.fillStyle = pattern + ctx.fillRect(0, 0, 20, 20) - var imageData = ctx.getImageData(0,0,20,20); - assert.equal(20, imageData.width); - assert.equal(20, imageData.height); - assert.equal(1600, imageData.data.length); + var imageData = ctx.getImageData(0, 0, 20, 20) + assert.equal(20, imageData.width) + assert.equal(20, imageData.height) + assert.equal(1600, imageData.data.length) - var i=0, b = true; - while (i= 6) - obj[Symbol.toPrimitive] = function () { return 0.89; }; - else - obj.valueOf = function () { return 0.89; }; - ctx.resetTransform(); - testAngle(obj, 0.89); + var obj = Object.create(null) + if (+process.version.match(/\d+/) >= 6) { obj[Symbol.toPrimitive] = function () { return 0.89 } } else { obj.valueOf = function () { return 0.89 } } + ctx.resetTransform() + testAngle(obj, 0.89) // NaN - ctx.resetTransform(); - ctx.rotate(0.91); - testAngle(NaN, 0.91); + ctx.resetTransform() + ctx.rotate(0.91) + testAngle(NaN, 0.91) // Infinite value - ctx.resetTransform(); - ctx.rotate(0.94); - testAngle(-Infinity, 0.94); + ctx.resetTransform() + ctx.rotate(0.94) + testAngle(-Infinity, 0.94) - function testAngle(angle, expected){ - ctx.rotate(angle); + function testAngle (angle, expected) { + ctx.rotate(angle) - var mat = ctx.currentTransform; - var sin = Math.sin(expected); - var cos = Math.cos(expected); + var mat = ctx.currentTransform + var sin = Math.sin(expected) + var cos = Math.cos(expected) - assert.ok(Math.abs(mat.m11 - cos) < Number.EPSILON); - assert.ok(Math.abs(mat.m12 - sin) < Number.EPSILON); - assert.ok(Math.abs(mat.m21 + sin) < Number.EPSILON); - assert.ok(Math.abs(mat.m22 - cos) < Number.EPSILON); + assert.ok(Math.abs(mat.m11 - cos) < Number.EPSILON) + assert.ok(Math.abs(mat.m12 - sin) < Number.EPSILON) + assert.ok(Math.abs(mat.m21 + sin) < Number.EPSILON) + assert.ok(Math.abs(mat.m22 - cos) < Number.EPSILON) } - }); + }) it('Context2d#drawImage()', function () { - var canvas = createCanvas(500, 500); - var ctx = canvas.getContext('2d'); + var canvas = createCanvas(500, 500) + var ctx = canvas.getContext('2d') // Drawing canvas to itself - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, 500, 500); - ctx.fillStyle = 'black'; - ctx.fillRect(5, 5, 10, 10); - ctx.drawImage(canvas, 20, 20); - - var imgd = ctx.getImageData(0, 0, 500, 500); - var data = imgd.data; - var count = 0; - - for(var i = 0; i < 500 * 500 * 4; i += 4){ - if(data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) - count++; + ctx.fillStyle = 'white' + ctx.fillRect(0, 0, 500, 500) + ctx.fillStyle = 'black' + ctx.fillRect(5, 5, 10, 10) + ctx.drawImage(canvas, 20, 20) + + var imgd = ctx.getImageData(0, 0, 500, 500) + var data = imgd.data + var count = 0 + + for (var i = 0; i < 500 * 500 * 4; i += 4) { + if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) { count++ } } - assert.strictEqual(count, 10 * 10 * 2); + assert.strictEqual(count, 10 * 10 * 2) // Drawing zero-width image - ctx.drawImage(canvas, 0, 0, 0, 0, 0, 0, 0, 0); - ctx.drawImage(canvas, 0, 0, 0, 0, 1, 1, 1, 1); - ctx.drawImage(canvas, 1, 1, 1, 1, 0, 0, 0, 0); - ctx.fillStyle = 'white'; - ctx.fillRect(0, 0, 500, 500); - - imgd = ctx.getImageData(0, 0, 500, 500); - data = imgd.data; - count = 0; - - for(i = 0; i < 500 * 500 * 4; i += 4){ - if(data[i] === 255 && data[i + 1] === 255 && data[i + 2] === 255) - count++; + ctx.drawImage(canvas, 0, 0, 0, 0, 0, 0, 0, 0) + ctx.drawImage(canvas, 0, 0, 0, 0, 1, 1, 1, 1) + ctx.drawImage(canvas, 1, 1, 1, 1, 0, 0, 0, 0) + ctx.fillStyle = 'white' + ctx.fillRect(0, 0, 500, 500) + + imgd = ctx.getImageData(0, 0, 500, 500) + data = imgd.data + count = 0 + + for (i = 0; i < 500 * 500 * 4; i += 4) { + if (data[i] === 255 && data[i + 1] === 255 && data[i + 2] === 255) { count++ } } - assert.strictEqual(count, 500 * 500); - }); + assert.strictEqual(count, 500 * 500) + }) it('Context2d#SetFillColor()', function () { - var canvas = createCanvas(2, 2); - var ctx = canvas.getContext('2d'); + var canvas = createCanvas(2, 2) + var ctx = canvas.getContext('2d') - ctx.fillStyle = '#808080'; - ctx.fillRect(0, 0, 2, 2); - var data = ctx.getImageData(0, 0, 2, 2).data; + ctx.fillStyle = '#808080' + ctx.fillRect(0, 0, 2, 2) + var data = ctx.getImageData(0, 0, 2, 2).data data.forEach(function (byte, index) { if (index + 1 & 3) - assert.strictEqual(byte, 128); + assert.strictEqual(byte, 128) else - assert.strictEqual(byte, 255); - }); - }); - -}); + assert.strictEqual(byte, 255) + }) + }) +}) diff --git a/test/dommatrix.test.js b/test/dommatrix.test.js index 8e1571713..e9b715567 100644 --- a/test/dommatrix.test.js +++ b/test/dommatrix.test.js @@ -9,12 +9,12 @@ const assert = require('assert') // This doesn't need to be precise; we're not testing the engine's trig // implementations. const TOLERANCE = 0.001 -function assertApprox(actual, expected, tolerance) { +function assertApprox (actual, expected, tolerance) { if (typeof tolerance !== 'number') tolerance = TOLERANCE assert.ok(expected > actual - tolerance && expected < actual + tolerance, `Expected ${expected} to equal ${actual} +/- ${tolerance}`) } -function assertApproxDeep(actual, expected, tolerance) { +function assertApproxDeep (actual, expected, tolerance) { expected.forEach(function (value, index) { assertApprox(actual[index], value) }) @@ -529,14 +529,14 @@ describe('DOMMatrix', function () { describe('transformPoint', function () { it('works', function () { var x = new DOMMatrix() - var r = x.transformPoint({x: 1, y: 2, z: 3}) + var r = x.transformPoint({ x: 1, y: 2, z: 3 }) assert.strictEqual(r.x, 1) assert.strictEqual(r.y, 2) assert.strictEqual(r.z, 3) assert.strictEqual(r.w, 1) x.rotateSelf(70) - r = x.transformPoint({x: 2, y: 3, z: 4}) + r = x.transformPoint({ x: 2, y: 3, z: 4 }) assertApprox(r.x, -2.13503) assertApprox(r.y, 2.905445) assert.strictEqual(r.z, 4) diff --git a/test/image.test.js b/test/image.test.js index f8d37e465..3a64569c1 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -6,7 +6,7 @@ * Module dependencies. */ -const {createCanvas, loadImage, rsvgVersion} = require('../'); +const { createCanvas, loadImage, rsvgVersion } = require('../'); const Image = require('../').Image const HAVE_SVG = rsvgVersion !== undefined; @@ -24,12 +24,12 @@ const bmp_dir = `${__dirname}/fixtures/bmp` describe('Image', function () { it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { - var img = new Image(); - assert.throws(function () { Image.prototype.width; }, /incompatible receiver/); - assert(!img.hasOwnProperty('width')); - assert('width' in img); - assert(Image.prototype.hasOwnProperty('width')); - }); + var img = new Image() + assert.throws(function () { Image.prototype.width }, /incompatible receiver/) + assert(!img.hasOwnProperty('width')) + assert('width' in img) + assert(Image.prototype.hasOwnProperty('width')) + }) it('loads JPEG image', function () { return loadImage(jpg_face).then((img) => { @@ -99,7 +99,7 @@ describe('Image', function () { if (!HAVE_SVG) this.skip(); const base64Enc = fs.readFileSync(svg_tree, 'base64') const dataURL = `data:image/svg+xml;base64,${base64Enc}` - return loadImage(dataURL).then((img) => { + return loadImage(dataURL).then((img) => { assert.strictEqual(img.onerror, null) assert.strictEqual(img.onload, null) assert.strictEqual(img.width, 200) @@ -112,7 +112,7 @@ describe('Image', function () { if (!HAVE_SVG) this.skip(); const utf8Encoded = fs.readFileSync(svg_tree, 'utf8') const dataURL = `data:image/svg+xml;utf8,${utf8Encoded}` - return loadImage(dataURL).then((img) => { + return loadImage(dataURL).then((img) => { assert.strictEqual(img.onerror, null) assert.strictEqual(img.onload, null) assert.strictEqual(img.width, 200) @@ -168,7 +168,7 @@ describe('Image', function () { it('captures errors from libjpeg', function (done) { const img = new Image() img.onerror = err => { - assert.equal(err.message, "JPEG datastream contains no image") + assert.equal(err.message, 'JPEG datastream contains no image') assert.strictEqual(img.complete, true) done() } @@ -229,7 +229,7 @@ describe('Image', function () { }) }) - it('should unbind Image#onload', function() { + it('should unbind Image#onload', function () { return loadImage(png_clock).then((img) => { let onloadCalled = 0 @@ -256,7 +256,7 @@ describe('Image', function () { }) }) - it('should unbind Image#onerror', function() { + it('should unbind Image#onerror', function () { return loadImage(png_clock).then((img) => { let onloadCalled = 0 let onerrorCalled = 0 @@ -313,25 +313,25 @@ describe('Image', function () { }) it('does not contain `source` property', function () { - var keys = Reflect.ownKeys(Image.prototype); - assert.ok(!keys.includes('source')); - assert.ok(!keys.includes('getSource')); - assert.ok(!keys.includes('setSource')); - }); + var keys = Reflect.ownKeys(Image.prototype) + assert.ok(!keys.includes('source')) + assert.ok(!keys.includes('getSource')) + assert.ok(!keys.includes('setSource')) + }) describe('supports BMP', function () { it('parses 1-bit image', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - assert.strictEqual(img.width, 111); - assert.strictEqual(img.height, 72); - done(); - }; + assert.strictEqual(img.width, 111) + assert.strictEqual(img.height, 72) + done() + } - img.onerror = err => { throw err; }; - img.src = path.join(bmp_dir, '1-bit.bmp'); - }); + img.onerror = err => { throw err } + img.src = path.join(bmp_dir, '1-bit.bmp') + }) it('parses 4-bit image', function (done) { let img = new Image(); @@ -349,32 +349,32 @@ describe('Image', function () { it('parses 8-bit image'); it('parses 24-bit image', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - assert.strictEqual(img.width, 2); - assert.strictEqual(img.height, 2); + assert.strictEqual(img.width, 2) + assert.strictEqual(img.height, 2) testImgd(img, [ 0, 0, 255, 255, 0, 255, 0, 255, 255, 0, 0, 255, - 255, 255, 255, 255, - ]); + 255, 255, 255, 255 + ]) - done(); - }; + done() + } - img.onerror = err => { throw err; }; - img.src = path.join(bmp_dir, '24-bit.bmp'); - }); + img.onerror = err => { throw err } + img.src = path.join(bmp_dir, '24-bit.bmp') + }) it('parses 32-bit image', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - assert.strictEqual(img.width, 4); - assert.strictEqual(img.height, 2); + assert.strictEqual(img.width, 4) + assert.strictEqual(img.height, 2) testImgd(img, [ 0, 0, 255, 255, @@ -384,52 +384,52 @@ describe('Image', function () { 0, 0, 255, 127, 0, 255, 0, 127, 255, 0, 0, 127, - 255, 255, 255, 127, - ]); - - done(); - }; + 255, 255, 255, 127 + ]) - img.onerror = err => { throw err; }; - img.src = fs.readFileSync(path.join(bmp_dir, '32-bit.bmp')); // Also tests loading from buffer - }); + done() + } + + img.onerror = err => { throw err } + img.src = fs.readFileSync(path.join(bmp_dir, '32-bit.bmp')) // Also tests loading from buffer + }) it('parses minimal BMP', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - assert.strictEqual(img.width, 1); - assert.strictEqual(img.height, 1); + assert.strictEqual(img.width, 1) + assert.strictEqual(img.height, 1) testImgd(img, [ - 255, 0, 0, 255, - ]); - - done(); - }; + 255, 0, 0, 255 + ]) - img.onerror = err => { throw err; }; - img.src = path.join(bmp_dir, 'min.bmp'); - }); + done() + } + + img.onerror = err => { throw err } + img.src = path.join(bmp_dir, 'min.bmp') + }) it('properly handles negative height', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - assert.strictEqual(img.width, 1); - assert.strictEqual(img.height, 2); + assert.strictEqual(img.width, 1) + assert.strictEqual(img.height, 2) testImgd(img, [ 255, 0, 0, 255, - 0, 255, 0, 255, - ]); - - done(); - }; + 0, 255, 0, 255 + ]) - img.onerror = err => { throw err; }; - img.src = path.join(bmp_dir, 'negative-height.bmp'); - }); + done() + } + + img.onerror = err => { throw err } + img.src = path.join(bmp_dir, 'negative-height.bmp') + }) it('color palette', function (done) { let img = new Image(); @@ -460,40 +460,40 @@ describe('Image', function () { it('V5 header'); it('catches BMP errors', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - throw new Error('Invalid image should not be loaded properly'); - }; + throw new Error('Invalid image should not be loaded properly') + } img.onerror = err => { - let msg = 'Error while processing file header - unexpected end of file'; - assert.strictEqual(err.message, msg); - done(); - }; + let msg = 'Error while processing file header - unexpected end of file' + assert.strictEqual(err.message, msg) + done() + } - img.src = Buffer.from('BM'); - }); + img.src = Buffer.from('BM') + }) it('BMP bomb', function (done) { - let img = new Image(); + let img = new Image() img.onload = () => { - throw new Error('Invalid image should not be loaded properly'); - }; + throw new Error('Invalid image should not be loaded properly') + } img.onerror = err => { - done(); - }; + done() + } - img.src = path.join(bmp_dir, 'bomb.bmp'); - }); + img.src = path.join(bmp_dir, 'bomb.bmp') + }) - function testImgd(img, data){ - let ctx = createCanvas(img.width, img.height).getContext('2d'); - ctx.drawImage(img, 0, 0); - var actualData = ctx.getImageData(0, 0, img.width, img.height).data; - assert.strictEqual(String(actualData), String(data)); + function testImgd (img, data) { + let ctx = createCanvas(img.width, img.height).getContext('2d') + ctx.drawImage(img, 0, 0) + var actualData = ctx.getImageData(0, 0, img.width, img.height).data + assert.strictEqual(String(actualData), String(data)) } - }); + }) }) diff --git a/test/imageData.test.js b/test/imageData.test.js index cfffe9aaf..26acd61f4 100644 --- a/test/imageData.test.js +++ b/test/imageData.test.js @@ -3,14 +3,14 @@ 'use strict' const createImageData = require('../').createImageData -const ImageData = require('../').ImageData; +const ImageData = require('../').ImageData const assert = require('assert') describe('ImageData', function () { it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () { - assert.throws(function () { ImageData.prototype.width; }, /incompatible receiver/); - }); + assert.throws(function () { ImageData.prototype.width }, /incompatible receiver/) + }) it('should throw with invalid numeric arguments', function () { assert.throws(() => { createImageData(0, 0) }, /width is zero/) @@ -33,7 +33,7 @@ describe('ImageData', function () { assert.throws(() => { createImageData(new Uint8ClampedArray(3), 0) }, /source width is zero/) // Note: Some errors thrown by browsers are not thrown by node-canvas // because our ImageData can support different BPPs. - }); + }) it('should construct with Uint8ClampedArray', function () { let data, imageData @@ -51,7 +51,7 @@ describe('ImageData', function () { assert.strictEqual(imageData.height, 4) assert(imageData.data instanceof Uint8ClampedArray) assert.strictEqual(imageData.data.length, 48) - }); + }) it('should construct with Uint16Array', function () { let data = new Uint16Array(2 * 3 * 2) @@ -67,5 +67,5 @@ describe('ImageData', function () { assert.strictEqual(imagedata.height, 4) assert(imagedata.data instanceof Uint16Array) assert.strictEqual(imagedata.data.length, 24) - }); -}); + }) +}) diff --git a/test/public/tests.js b/test/public/tests.js index 31ee6aaca..2b67ad680 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -585,7 +585,7 @@ tests['createRadialGradient()'] = function (ctx) { ctx.fillRect(0, 0, 150, 150) } -tests['globalAlpha'] = function (ctx) { +tests.globalAlpha = function (ctx) { ctx.globalAlpha = 0.5 ctx.fillStyle = 'rgba(0,0,0,0.5)' ctx.strokeRect(0, 0, 50, 50) @@ -618,7 +618,7 @@ tests['globalAlpha 2'] = function (ctx) { } } -tests['fillStyle'] = function (ctx) { +tests.fillStyle = function (ctx) { for (var i = 0; i < 6; i++) { for (var j = 0; j < 6; j++) { ctx.fillStyle = 'rgb(' + Math.floor(255 - 42.5 * i) + ',' + Math.floor(255 - 42.5 * j) + ',0)' @@ -627,7 +627,7 @@ tests['fillStyle'] = function (ctx) { } } -tests['strokeStyle'] = function (ctx) { +tests.strokeStyle = function (ctx) { for (var i = 0; i < 6; i++) { for (var j = 0; j < 6; j++) { ctx.strokeStyle = 'rgb(0,' + Math.floor(255 - 42.5 * i) + ',' + @@ -662,7 +662,7 @@ tests['floating point coordinates'] = function (ctx) { ctx.stroke() } -tests['lineWidth'] = function (ctx) { +tests.lineWidth = function (ctx) { for (var i = 0; i < 10; i++) { ctx.lineWidth = 1 + i ctx.beginPath() @@ -718,7 +718,7 @@ tests['lineCap default'] = function (ctx) { ctx.stroke() } -tests['lineCap'] = function (ctx) { +tests.lineCap = function (ctx) { ctx.beginPath() ctx.lineWidth = 10.0 ctx.lineCap = 'round' @@ -728,7 +728,7 @@ tests['lineCap'] = function (ctx) { ctx.stroke() } -tests['lineJoin'] = function (ctx) { +tests.lineJoin = function (ctx) { ctx.beginPath() ctx.lineWidth = 10.0 ctx.lineJoin = 'round' @@ -738,7 +738,7 @@ tests['lineJoin'] = function (ctx) { ctx.stroke() } -tests['states'] = function (ctx) { +tests.states = function (ctx) { ctx.save() ctx.rect(50, 50, 100, 100) ctx.stroke() @@ -1325,7 +1325,7 @@ tests['known bug #416'] = function (ctx, done) { img1.src = imageSrc('existing.png') } -tests['shadowBlur'] = function (ctx) { +tests.shadowBlur = function (ctx) { ctx.fillRect(150, 10, 20, 20) ctx.lineTo(20, 5) @@ -1351,7 +1351,7 @@ tests['shadowBlur'] = function (ctx) { ctx.fillRect(150, 150, 20, 20) } -tests['shadowColor'] = function (ctx) { +tests.shadowColor = function (ctx) { ctx.fillRect(150, 10, 20, 20) ctx.lineTo(20, 5) @@ -2249,7 +2249,7 @@ tests['putImageData() png data 3'] = function (ctx, done) { img.src = imageSrc('state.png') } -tests['setLineDash'] = function (ctx) { +tests.setLineDash = function (ctx) { ctx.setLineDash([10, 5, 25, 15]) ctx.lineWidth = 14 @@ -2283,7 +2283,7 @@ tests['setLineDash'] = function (ctx) { line([0, 3, 0, 0], 'green') // should be empty } -tests['lineDashOffset'] = function (ctx) { +tests.lineDashOffset = function (ctx) { ctx.setLineDash([10, 5, 25, 15]) ctx.lineWidth = 4 @@ -2310,7 +2310,7 @@ tests['lineDashOffset'] = function (ctx) { line(60, 'orange') line(-Infinity) line(70, 'purple') - line(void 0) + line(undefined) line(80, 'black') line(ctx.lineDashOffset + 10) diff --git a/util/win_jpeg_lookup.js b/util/win_jpeg_lookup.js index 82869945a..40a1e03cf 100644 --- a/util/win_jpeg_lookup.js +++ b/util/win_jpeg_lookup.js @@ -5,17 +5,17 @@ if (process.arch === 'x64') { paths.unshift('C:/libjpeg-turbo64') } -paths.forEach(function(path){ +paths.forEach(function (path) { if (exists(path)) { process.stdout.write(path) process.exit() } }) -function exists(path) { +function exists (path) { try { return fs.lstatSync(path).isDirectory() - } catch(e) { + } catch (e) { return false } }