From ad0a41f41e394f85ee94c8b17b470f0350fa1ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Mon, 19 Nov 2018 16:24:55 +0100 Subject: [PATCH 01/56] Extracted code for FbDev support from #571 to make it easier to review and approve. It's a squashed version with the final code, for references on the development history and commit logs go to the original issue. --- binding.gyp | 10 ++- examples/simple_fbdev.js | 39 ++++++++++ src/Backends.cc | 8 +++ src/Canvas.cc | 4 +- src/backend/Backend.cc | 7 ++ src/backend/Backend.h | 1 + src/backend/FBDevBackend.cc | 140 ++++++++++++++++++++++++++++++++++++ src/backend/FBDevBackend.h | 35 +++++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 examples/simple_fbdev.js create mode 100644 src/backend/FBDevBackend.cc create mode 100644 src/backend/FBDevBackend.h diff --git a/binding.gyp b/binding.gyp index e770b86c7..5d11e00d1 100644 --- a/binding.gyp +++ b/binding.gyp @@ -145,6 +145,11 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' } }], + ['has_FBDev=="true"', + { + 'defines': ['HAS_FBDEV'], + 'sources': ['src/backend/FBDevBackend.cc'] + }], ['with_jpeg=="true"', { 'defines': [ 'HAVE_JPEG' @@ -216,7 +221,10 @@ }] ] }] - ] + ], + 'variables': { + 'has_FBDev%': 'true', + } } ] } diff --git a/examples/simple_fbdev.js b/examples/simple_fbdev.js new file mode 100644 index 000000000..a1f28df5c --- /dev/null +++ b/examples/simple_fbdev.js @@ -0,0 +1,39 @@ +#!/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] || '/dev/fb0' + +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) + +var outPath = join(__dirname, 'rectangle.png') + +canvas.createPNGStream().pipe(fs.createWriteStream(outPath)) diff --git a/src/Backends.cc b/src/Backends.cc index 6490ab6e0..880ae076d 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(Handle target) { @@ -14,5 +18,9 @@ void Backends::Initialize(Handle target) { PdfBackend::Initialize(obj); SvgBackend::Initialize(obj); + #ifdef HAS_FBDEV + FBDevBackend::Initialize(obj); + #endif + target->Set(Nan::New("Backends").ToLocalChecked(), obj); } diff --git a/src/Canvas.cc b/src/Canvas.cc index 1ffed6b3d..e7d97259b 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -25,6 +25,7 @@ #include "JPEGStream.h" #endif +#include "backend/FBDevBackend.h" #include "backend/ImageBackend.h" #include "backend/PdfBackend.h" #include "backend/SvgBackend.h" @@ -104,7 +105,8 @@ NAN_METHOD(Canvas::New) { backend = new ImageBackend(width, height); } else if (info[0]->IsObject()) { - if (Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || + if (Nan::New(FBDevBackend::constructor)->HasInstance(info[0]) || + Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || Nan::New(PdfBackend::constructor)->HasInstance(info[0]) || Nan::New(SvgBackend::constructor)->HasInstance(info[0])) { backend = Nan::ObjectWrap::Unwrap(Nan::To(info[0]).ToLocalChecked()); diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 34ef541f5..0ea3b7af2 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -1,6 +1,13 @@ #include "Backend.h" +Backend::Backend(string name) + : name(name) + , width(0) + , height(0) + , surface(NULL) + , canvas(NULL) +{} Backend::Backend(string name, int width, int height) : name(name) , width(width) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 75ae37aad..5bbc80f4e 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -28,6 +28,7 @@ class Backend : public Nan::ObjectWrap cairo_surface_t* surface; Canvas* canvas; + Backend(string name); Backend(string name, int width, int height); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc new file mode 100644 index 000000000..581be177c --- /dev/null +++ b/src/backend/FBDevBackend.cc @@ -0,0 +1,140 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "FBDevBackend.h" + + +using namespace v8; + + +FBDevBackend::FBDevBackend(string deviceName) + : Backend("fbdev") +{ + // 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_FSCREENINFO, &this->fb_finfo, + "Error reading fixed framebuffer information"); + + // Map the device to memory + this->fb_data = (unsigned char*) mmap(0, this->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"); +} + +FBDevBackend::~FBDevBackend() +{ + this->destroySurface(); + + munmap(this->fb_data, this->fb_finfo.smem_len); + close(this->fb_fd); +} + + +void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, + string errmsg) +{ + if(ioctl(this->fb_fd, request, data) == -1) + throw FBDevBackendException(errmsg.c_str()); +} + + +cairo_surface_t* FBDevBackend::createSurface() +{ + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + this->width = fb_vinfo.xres; + this->height = fb_vinfo.yres; + + // switch through bpp and decide on which format for the cairo surface to use + cairo_format_t format; + switch(fb_vinfo.bits_per_pixel) + { + case 16: format = CAIRO_FORMAT_RGB16_565; break; + case 32: format = CAIRO_FORMAT_ARGB32; break; + + default: + throw FBDevBackendException("Only valid formats are RGB16_565 & ARGB32"); + } + + // create cairo surface from data + this->surface = cairo_image_surface_create_for_data(this->fb_data, format, + this->width, this->height, fb_finfo.line_length); + + return this->surface; +} + + +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"); + + Backend::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"); + + Backend::setHeight(width); +} + + +Nan::Persistent FBDevBackend::constructor; + +void FBDevBackend::Initialize(Handle target) +{ + Nan::HandleScope scope; + + Local ctor = Nan::New(FBDevBackend::New); + FBDevBackend::constructor.Reset(ctor); + ctor->InstanceTemplate()->SetInternalFieldCount(1); + ctor->SetClassName(Nan::New("FBDevBackend").ToLocalChecked()); + target->Set(Nan::New("FBDevBackend").ToLocalChecked(), ctor->GetFunction()); +} + +NAN_METHOD(FBDevBackend::New) +{ + string fbDevice = "/dev/fb0"; + if(info[0]->IsString()) fbDevice = *String::Utf8Value(info[0].As()); + + FBDevBackend* backend = new FBDevBackend(fbDevice); + + 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..55e90e51c --- /dev/null +++ b/src/backend/FBDevBackend.h @@ -0,0 +1,35 @@ +#ifndef __FBDEV_BACKEND_H__ +#define __FBDEV_BACKEND_H__ + #include +#include + #include + #include + #include "Backend.h" + using namespace std; + class FBDevBackend : public Backend +{ + private: + int fb_fd; + struct fb_fix_screeninfo fb_finfo; + unsigned char* fb_data; + ~FBDevBackend(); + void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); + cairo_surface_t* createSurface(); + void setWidth(int width); + void setHeight(int height); + public: + FBDevBackend(string deviceName); + static Nan::Persistent constructor; + static void Initialize(v8::Handle 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 From 3a1d55d6e8313731a296f447a712ad6877c67bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 02:01:53 +0100 Subject: [PATCH 02/56] Fix linting --- benchmarks/run.js | 10 +- lib/DOMMatrix.js | 96 +- lib/bindings.js | 4 +- lib/canvas.js | 86 +- lib/context2d.js | 152 +-- lib/image.js | 56 +- lib/jpegstream.js | 42 +- lib/parse-font.js | 19 +- lib/pdfstream.js | 38 +- lib/pngstream.js | 42 +- test/canvas.test.js | 2397 ++++++++++++++++++++------------------- test/dommatrix.test.js | 20 +- test/image.test.js | 180 +-- test/imageData.test.js | 14 +- util/win_jpeg_lookup.js | 6 +- 15 files changed, 1594 insertions(+), 1568 deletions(-) diff --git a/benchmarks/run.js b/benchmarks/run.js index e088e0692..4914ea97b 100644 --- a/benchmarks/run.js +++ b/benchmarks/run.js @@ -65,8 +65,8 @@ function done (benchmark, times, start, isAsync) { // node-canvas bm('fillStyle= name', function () { - ctx.fillStyle = "transparent"; -}); + ctx.fillStyle = 'transparent' +}) bm('lineTo()', function () { ctx.lineTo(0, 50) @@ -138,11 +138,11 @@ bm('moveTo() / arc() / stroke()', function () { ctx.beginPath() ctx.arc(75, 75, 50, 0, Math.PI * 2, true) // Outer circle ctx.moveTo(110, 75) - ctx.arc(75, 75, 35, 0, Math.PI, false) // Mouth + ctx.arc(75, 75, 35, 0, Math.PI, false) // Mouth ctx.moveTo(65, 65) - ctx.arc(60, 65, 5, 0, Math.PI * 2, true) // Left eye + ctx.arc(60, 65, 5, 0, Math.PI * 2, true) // Left eye ctx.moveTo(95, 65) - ctx.arc(90, 65, 5, 0, Math.PI * 2, true) // Right eye + ctx.arc(90, 65, 5, 0, Math.PI * 2, true) // Right eye ctx.stroke() }) diff --git a/lib/DOMMatrix.js b/lib/DOMMatrix.js index 3c5262449..cf4485395 100644 --- a/lib/DOMMatrix.js +++ b/lib/DOMMatrix.js @@ -2,7 +2,7 @@ // 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'") } @@ -20,15 +20,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}`) @@ -41,14 +41,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': @@ -158,15 +158,15 @@ DOMMatrix.prototype.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 } @@ -175,7 +175,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 @@ -184,31 +184,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 () { @@ -226,7 +226,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 @@ -234,7 +234,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++) { @@ -383,10 +383,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 @@ -422,7 +422,7 @@ DOMMatrix.prototype.skewYSelf = function (sy) { return this } -DOMMatrix.prototype.flipX = function () { +DOMMatrix.prototype.flipX = function () { return newInstance(multiply([ -1, 0, 0, 0, 0, 1, 0, 0, @@ -468,12 +468,12 @@ DOMMatrix.prototype.transformPoint = function (point) { return new DOMPoint(nx, ny, nz, nw) } -DOMMatrix.prototype.toFloat32Array = function () { +DOMMatrix.prototype.toFloat32Array = function () { return Float32Array.from(this._values) } -DOMMatrix.prototype.toFloat64Array = function () { +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 ec16b92ea..72a729366 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas @@ -25,9 +25,9 @@ const FORMATS = ['image/png', 'image/jpeg'] * @api public */ -Canvas.prototype.inspect = function(){ - return '[Canvas ' + this.width + 'x' + this.height + ']'; -}; +Canvas.prototype.inspect = function () { + return '[Canvas ' + this.width + 'x' + this.height + ']' +} /** * Get a context object. @@ -39,13 +39,13 @@ Canvas.prototype.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 } -}; +} /** * Create a `PNGStream` for `this` canvas. @@ -66,9 +66,9 @@ Canvas.prototype.getContext = function (contextType, contextAttributes) { * @public */ Canvas.prototype.pngStream = -Canvas.prototype.createPNGStream = function(options){ - return new PNGStream(this, options); -}; +Canvas.prototype.createPNGStream = function (options) { + return new PNGStream(this, options) +} /** * Create a `PDFStream` for `this` canvas. @@ -77,9 +77,9 @@ Canvas.prototype.createPNGStream = function(options){ * @public */ Canvas.prototype.pdfStream = -Canvas.prototype.createPDFStream = function(){ - return new PDFStream(this); -}; +Canvas.prototype.createPDFStream = function () { + return new PDFStream(this) +} /** * Create a `JPEGStream` for `this` canvas. @@ -94,9 +94,9 @@ Canvas.prototype.createPDFStream = function(){ * @public */ Canvas.prototype.jpegStream = -Canvas.prototype.createJPEGStream = function(options){ - return new JPEGStream(this, options); -}; +Canvas.prototype.createJPEGStream = function (options) { + return new JPEGStream(this, options) +} /** * Returns a data URI. Pass a function for async operation (non-standard). @@ -110,7 +110,7 @@ Canvas.prototype.createJPEGStream = function(options){ * @return {String} data URL if synchronous (callback omitted) * @api public */ -Canvas.prototype.toDataURL = function(a1, a2, a3){ +Canvas.prototype.toDataURL = function (a1, a2, a3) { // valid arg patterns (args -> [type, opts, fn]): // [] -> ['image/png', null, null] // [qual] -> ['image/png', null, null] @@ -129,51 +129,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).toString('base64')}` } -}; +} diff --git a/lib/context2d.js b/lib/context2d.js index b9c28e6ea..b5237593c 100644 --- a/lib/context2d.js +++ b/lib/context2d.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - Context2d @@ -22,7 +22,7 @@ const DOMMatrix = require('./DOMMatrix').DOMMatrix * Text baselines. */ -var baselines = ['alphabetic', 'top', 'bottom', 'middle', 'ideographic', 'hanging']; +var baselines = ['alphabetic', 'top', 'bottom', 'middle', 'ideographic', 'hanging'] /** * Create a pattern from `Image` or `Canvas`. @@ -33,9 +33,9 @@ var baselines = ['alphabetic', 'top', 'bottom', 'middle', 'ideographic', 'hangin * @api public */ -Context2d.prototype.createPattern = function(image, repetition){ - return new CanvasPattern(image, repetition || 'repeat'); -}; +Context2d.prototype.createPattern = function (image, repetition) { + return new CanvasPattern(image, repetition || 'repeat') +} /** * Create a linear gradient at the given point `(x0, y0)` and `(x1, y1)`. @@ -48,9 +48,9 @@ Context2d.prototype.createPattern = function(image, repetition){ * @api public */ -Context2d.prototype.createLinearGradient = function(x0, y0, x1, y1){ - return new CanvasGradient(x0, y0, x1, y1); -}; +Context2d.prototype.createLinearGradient = function (x0, y0, x1, y1) { + return new CanvasGradient(x0, y0, x1, y1) +} /** * Create a radial gradient at the given point `(x0, y0)` and `(x1, y1)` @@ -66,9 +66,9 @@ Context2d.prototype.createLinearGradient = function(x0, y0, x1, y1){ * @api public */ -Context2d.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1){ - return new CanvasGradient(x0, y0, r0, x1, y1, r1); -}; +Context2d.prototype.createRadialGradient = function (x0, y0, r0, x1, y1, r1) { + return new CanvasGradient(x0, y0, r0, x1, y1, r1) +} /** * Reset transform matrix to identity, then apply the given args. @@ -77,10 +77,10 @@ Context2d.prototype.createRadialGradient = function(x0, y0, r0, x1, y1, r1){ * @api public */ -Context2d.prototype.setTransform = function(){ - this.resetTransform(); - this.transform.apply(this, arguments); -}; +Context2d.prototype.setTransform = function () { + this.resetTransform() + this.transform.apply(this, arguments) +} Object.defineProperty(Context2d.prototype, 'currentTransform', { get: function () { @@ -103,16 +103,16 @@ Object.defineProperty(Context2d.prototype, 'currentTransform', { * @api public */ -Context2d.prototype.__defineSetter__('fillStyle', function(val){ - if (val instanceof CanvasGradient - || val instanceof CanvasPattern) { - this.lastFillStyle = val; - this._setFillPattern(val); +Context2d.prototype.__defineSetter__('fillStyle', function (val) { + if (val instanceof CanvasGradient || + val instanceof CanvasPattern) { + this.lastFillStyle = val + this._setFillPattern(val) } else { - this.lastFillStyle = undefined; - this._setFillColor(String(val)); + this.lastFillStyle = undefined + this._setFillColor(String(val)) } -}); +}) /** * Get previous fill style. @@ -121,9 +121,9 @@ Context2d.prototype.__defineSetter__('fillStyle', function(val){ * @api public */ -Context2d.prototype.__defineGetter__('fillStyle', function(){ - return this.lastFillStyle || this.fillColor; -}); +Context2d.prototype.__defineGetter__('fillStyle', function () { + return this.lastFillStyle || this.fillColor +}) /** * Set the stroke style with the given css color string. @@ -131,15 +131,15 @@ Context2d.prototype.__defineGetter__('fillStyle', function(){ * @api public */ -Context2d.prototype.__defineSetter__('strokeStyle', function(val){ - if (val instanceof CanvasGradient - || val instanceof CanvasPattern) { - this.lastStrokeStyle = val; - this._setStrokePattern(val); +Context2d.prototype.__defineSetter__('strokeStyle', function (val) { + if (val instanceof CanvasGradient || + val instanceof CanvasPattern) { + this.lastStrokeStyle = val + this._setStrokePattern(val) } else { - this._setStrokeColor(String(val)); + this._setStrokeColor(String(val)) } -}); +}) /** * Get previous stroke style. @@ -148,9 +148,9 @@ Context2d.prototype.__defineSetter__('strokeStyle', function(val){ * @api public */ -Context2d.prototype.__defineGetter__('strokeStyle', function(){ - return this.lastStrokeStyle || this.strokeColor; -}); +Context2d.prototype.__defineGetter__('strokeStyle', function () { + return this.lastStrokeStyle || this.strokeColor +}) /** * Set font. @@ -159,21 +159,21 @@ Context2d.prototype.__defineGetter__('strokeStyle', function(){ * @api public */ -Context2d.prototype.__defineSetter__('font', function(val){ - if (!val) return; - if ('string' == typeof val) { - var font; +Context2d.prototype.__defineSetter__('font', function (val) { + if (!val) return + if (typeof val === 'string') { + var font if (font = parseFont(val)) { - this.lastFontString = val; + this.lastFontString = val this._setFont( - font.weight + font.weight , font.style , font.size , font.unit - , font.family); + , font.family) } } -}); +}) /** * Get the current font. @@ -181,9 +181,9 @@ Context2d.prototype.__defineSetter__('font', function(val){ * @api public */ -Context2d.prototype.__defineGetter__('font', function(){ - return this.lastFontString || '10px sans-serif'; -}); +Context2d.prototype.__defineGetter__('font', function () { + return this.lastFontString || '10px sans-serif' +}) /** * Set text baseline. @@ -191,14 +191,14 @@ Context2d.prototype.__defineGetter__('font', function(){ * @api public */ -Context2d.prototype.__defineSetter__('textBaseline', function(val){ - if (!val) return; - var n = baselines.indexOf(val); +Context2d.prototype.__defineSetter__('textBaseline', function (val) { + if (!val) return + var n = baselines.indexOf(val) if (~n) { - this.lastBaseline = val; - this._setTextBaseline(n); + this.lastBaseline = val + this._setTextBaseline(n) } -}); +}) /** * Get the current baseline setting. @@ -206,9 +206,9 @@ Context2d.prototype.__defineSetter__('textBaseline', function(val){ * @api public */ -Context2d.prototype.__defineGetter__('textBaseline', function(){ - return this.lastBaseline || 'alphabetic'; -}); +Context2d.prototype.__defineGetter__('textBaseline', function () { + return this.lastBaseline || 'alphabetic' +}) /** * Set text alignment. @@ -216,24 +216,24 @@ Context2d.prototype.__defineGetter__('textBaseline', function(){ * @api public */ -Context2d.prototype.__defineSetter__('textAlign', function(val){ +Context2d.prototype.__defineSetter__('textAlign', function (val) { switch (val) { case 'center': - this._setTextAlignment(0); - this.lastTextAlignment = val; - break; + this._setTextAlignment(0) + this.lastTextAlignment = val + break case 'left': case 'start': - this._setTextAlignment(-1); - this.lastTextAlignment = val; - break; + this._setTextAlignment(-1) + this.lastTextAlignment = val + break case 'right': case 'end': - this._setTextAlignment(1); - this.lastTextAlignment = val; - break; + this._setTextAlignment(1) + this.lastTextAlignment = val + break } -}); +}) /** * Get the current font. @@ -242,9 +242,9 @@ Context2d.prototype.__defineSetter__('textAlign', function(val){ * @api public */ -Context2d.prototype.__defineGetter__('textAlign', function(){ - return this.lastTextAlignment || 'start'; -}); +Context2d.prototype.__defineGetter__('textAlign', function () { + return this.lastTextAlignment || 'start' +}) /** * Create `ImageData` with the given dimensions or @@ -261,13 +261,13 @@ Context2d.prototype.createImageData = function (width, height) { height = width.height width = width.width } - var Bpp = this.canvas.stride / this.canvas.width; + var Bpp = this.canvas.stride / this.canvas.width var nBytes = Bpp * width * height - var arr; - if (this.pixelFormat === "RGB16_565") { - arr = new Uint16Array(nBytes / 2); + var arr + if (this.pixelFormat === 'RGB16_565') { + arr = new Uint16Array(nBytes / 2) } else { - arr = new Uint8ClampedArray(nBytes); + arr = new Uint8ClampedArray(nBytes) } - return new ImageData(arr, width, height); + return new ImageData(arr, width, height) } diff --git a/lib/image.js b/lib/image.js index ff84f2b24..a64b7796d 100644 --- a/lib/image.js +++ b/lib/image.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - Image @@ -12,15 +12,15 @@ const bindings = require('./bindings') const Image = module.exports = bindings.Image -const http = require("http") -const https = require("https") +const http = require('http') +const https = require('https') -const proto = Image.prototype; -const _getSource = proto.getSource; -const _setSource = proto.setSource; +const proto = Image.prototype +const _getSource = proto.getSource +const _setSource = proto.setSource -delete proto.getSource; -delete proto.setSource; +delete proto.getSource +delete proto.setSource Object.defineProperty(Image.prototype, 'src', { /** @@ -33,14 +33,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') { @@ -58,24 +58,24 @@ Object.defineProperty(Image.prototype, 'src', { const buffers = [] res.on('data', buffer => buffers.push(buffer)) res.on('end', () => { - setSource(this, Buffer.concat(buffers)); + setSource(this, Buffer.concat(buffers)) }) }).on('error', onerror) } 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 -}); +}) /** * Inspect image. @@ -86,19 +86,19 @@ Object.defineProperty(Image.prototype, 'src', { * @api public */ -Image.prototype.inspect = function(){ - return '[Image' - + (this.complete ? ':' + this.width + 'x' + this.height : '') - + (this.src ? ' ' + this.src : '') - + (this.complete ? ' complete' : '') - + ']'; -}; +Image.prototype.inspect = function () { + return '[Image' + + (this.complete ? ':' + this.width + 'x' + this.height : '') + + (this.src ? ' ' + this.src : '') + + (this.complete ? ' complete' : '') + + ']' +} -function getSource(img){ - return img._originalSource || _getSource.call(img); +function getSource (img) { + return img._originalSource || _getSource.call(img) } -function setSource(img, src, origSrc){ - _setSource.call(img, src); - img._originalSource = origSrc; +function setSource (img, src, origSrc) { + _setSource.call(img, src) + img._originalSource = origSrc } diff --git a/lib/jpegstream.js b/lib/jpegstream.js index e44ed4c65..92c5df80b 100644 --- a/lib/jpegstream.js +++ b/lib/jpegstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - JPEGStream @@ -10,8 +10,8 @@ * Module dependencies. */ -var Readable = require('stream').Readable; -var util = require('util'); +var Readable = require('stream').Readable +var util = require('util') /** * Initialize a `JPEGStream` with the given `canvas`. @@ -29,37 +29,37 @@ var util = require('util'); * @private */ -var JPEGStream = module.exports = function JPEGStream(canvas, options) { +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 a149c709c..8fb9551a7 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 837a034c3..b056c6e47 100644 --- a/lib/pdfstream.js +++ b/lib/pdfstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - PDFStream @@ -8,8 +8,8 @@ * Module dependencies. */ -var Readable = require('stream').Readable; -var util = require('util'); +var Readable = require('stream').Readable +var util = require('util') /** * Initialize a `PDFStream` with the given `canvas`. @@ -27,32 +27,32 @@ var util = require('util'); * @api public */ -var PDFStream = module.exports = function PDFStream(canvas) { +var PDFStream = module.exports = function PDFStream (canvas) { 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.canvas = canvas +} -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) } - }); -}; + }) +} diff --git a/lib/pngstream.js b/lib/pngstream.js index 3a03c78c7..7fac31129 100644 --- a/lib/pngstream.js +++ b/lib/pngstream.js @@ -1,4 +1,4 @@ -'use strict'; +'use strict' /*! * Canvas - PNGStream @@ -10,46 +10,46 @@ * Module dependencies. */ -var Readable = require('stream').Readable; -var util = require('util'); +var Readable = require('stream').Readable +var util = require('util') /** * @param {Canvas} canvas * @param {Object} options * @private */ -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/test/canvas.test.js b/test/canvas.test.js index 77e012648..3239d1b85 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -18,136 +18,140 @@ const Readable = require('stream').Readable describe('Canvas', function () { 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] - assert.deepEqual(actual, expected, 'Failed to parse: ' + str); + var 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' + + assert.deepEqual(actual, expected, 'Failed to parse: ' + str) } - }); + }) 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'); + + ['fillStyle', 'strokeStyle', 'shadowColor'].forEach(function (prop) { + ctx[prop] = '#FFFFFF' + assert.equal('#ffffff', ctx[prop], prop + ' #FFFFFF -> #ffffff, got ' + ctx[prop]) - ctx[prop] = '#FFF'; - assert.equal('#ffffff', ctx[prop], prop + ' #FFF -> #ffffff, got ' + ctx[prop]); + ctx[prop] = '#FFF' + assert.equal('#ffffff', ctx[prop], prop + ' #FFF -> #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] = '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.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,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,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.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]) - if ('shadowColor' == prop) return; + if (prop == 'shadowColor') return - var grad = ctx.createLinearGradient(0,0,0,150); - ctx[prop] = grad; - assert.strictEqual(grad, ctx[prop], prop + ' pattern getter failed'); - }); - }); + 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 = '#FFCCAA' + assert.equal('#ffccaa', ctx.fillStyle) - ctx.fillStyle = '#FCA'; - assert.equal('#ffccaa', ctx.fillStyle); + ctx.fillStyle = '#FCA' + assert.equal('#ffccaa', ctx.fillStyle) - ctx.fillStyle = '#fff'; - ctx.fillStyle = '#FGG'; - assert.equal('#ff0000', ctx.fillStyle); + ctx.fillStyle = '#fff' + ctx.fillStyle = '#FGG' + assert.equal('#ff0000', ctx.fillStyle) - ctx.fillStyle = '#fff'; - ctx.fillStyle = 'afasdfasdf'; - assert.equal('#ffffff', ctx.fillStyle); + ctx.fillStyle = '#fff' + ctx.fillStyle = 'afasdfasdf' + assert.equal('#ffffff', ctx.fillStyle) // #rgba and #rrggbbaa ctx.fillStyle = '#ffccaa80' @@ -159,108 +163,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') + var canvas = createCanvas(10, 10, 'hey') + assert.equal(canvas.type, 'image') + }) 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; @@ -275,332 +280,342 @@ 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 () { - var context, canvas = createCanvas(100, 200); + var context; var canvas = createCanvas(100, 200) - assert.equal(100, canvas.width); - assert.equal(200, canvas.height); + assert.equal(100, canvas.width) + assert.equal(200, canvas.height) - canvas = createCanvas(); - context = canvas.getContext("2d"); - assert.equal(0, canvas.width); - assert.equal(0, canvas.height); + canvas = createCanvas() + context = canvas.getContext('2d') + assert.equal(0, canvas.width) + assert.equal(0, canvas.height) - canvas.width = 50; - canvas.height = 50; - assert.equal(50, canvas.width); - assert.equal(50, canvas.height); - assert.equal(1, context.lineWidth); // #1095 - }); + canvas.width = 50 + canvas.height = 50 + assert.equal(50, canvas.width) + assert.equal(50, canvas.height) + assert.equal(1, context.lineWidth) // #1095 + }) - it('Canvas#stride', function() { - var canvas = createCanvas(24, 10); - assert.ok(canvas.stride >= 24, 'canvas.stride is too short'); - assert.ok(canvas.stride < 1024, 'canvas.stride seems too long'); + it('Canvas#stride', function () { + var canvas = createCanvas(24, 10) + assert.ok(canvas.stride >= 24, 'canvas.stride is too short') + 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 () { var canvas = createCanvas(200, 200) - , ctx = canvas.getContext('2d'); - assert.equal('10px sans-serif', ctx.font); - ctx.font = '15px Arial, sans-serif'; - assert.equal('15px Arial, sans-serif', ctx.font); - }); + var ctx = canvas.getContext('2d') + + assert.equal('10px sans-serif', ctx.font) + ctx.font = '15px Arial, sans-serif' + assert.equal('15px Arial, sans-serif', ctx.font) + }) 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 for (let i = 0; i < buf.length - 12; i++) { if (buf[i] === 0x70 && buf[i + 1] === 0x48 && buf[i + 2] === 0x59 && buf[i + 3] === 0x73) { // pHYs - assert.equal(buf[i + 4], 0); - assert.equal(buf[i + 5], 0); - assert.equal(buf[i + 6], 0x0e); - assert.equal(buf[i + 7], 0xc4); // x - assert.equal(buf[i + 8], 0); - assert.equal(buf[i + 9], 0); - assert.equal(buf[i + 10], 0x0e); - assert.equal(buf[i + 11], 0xc4); // y + assert.equal(buf[i + 4], 0) + assert.equal(buf[i + 5], 0) + assert.equal(buf[i + 6], 0x0e) + assert.equal(buf[i + 7], 0xc4) // x + assert.equal(buf[i + 8], 0) + assert.equal(buf[i + 9], 0) + assert.equal(buf[i + 10], 0x0e) + assert.equal(buf[i + 11], 0xc4) // y } } }) 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}); - }); - - describe('#toBuffer("raw")', function() { + 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 () { 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- @@ -615,284 +630,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') + + 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]); - }); + 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 () { + 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) - it("works, RGB24 format", function () { + assert.equal(0, imageData.data[0]) + assert.equal(0, imageData.data[1]) + }) + + 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) - it("works, RGB16_565 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, 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' }) + + 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]); - }); - }); + 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 @@ -900,807 +922,806 @@ describe('Canvas', function () { // Positive = going down from the baseline assert.ok(metrics.actualBoundingBoxDescent > 0) // ~4-5 - ctx.textBaseline = "bottom" - metrics = ctx.measureText("Alphabet") + ctx.textBaseline = 'bottom' + metrics = ctx.measureText('Alphabet') assert.ok(metrics.alphabeticBaseline > 0) // ~4-5 assert.ok(metrics.actualBoundingBoxAscent > 0) // On the baseline or slightly above assert.ok(metrics.actualBoundingBoxDescent <= 0) - }); - }); + }) + }) it('Context2d#currentTransform', function () { - var canvas = createCanvas(20, 20); - var ctx = canvas.getContext('2d'); - - ctx.scale(0.1, 0.3); - var actual = ctx.currentTransform; - assert.equal(actual.a, 0.1); - assert.equal(actual.b, 0); - assert.equal(actual.c, 0); - assert.equal(actual.d, 0.3); - assert.equal(actual.e, 0); - assert.equal(actual.f, 0); - }); + var canvas = createCanvas(20, 20) + var ctx = canvas.getContext('2d') + + ctx.scale(0.1, 0.3) + var actual = ctx.currentTransform + assert.equal(actual.a, 0.1) + assert.equal(actual.b, 0) + assert.equal(actual.c, 0) + assert.equal(actual.d, 0.3) + assert.equal(actual.e, 0) + assert.equal(actual.f, 0) + }) 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 () { - var ctx = createTestCanvas(); - var imageData = ctx.getImageData(0,0,3,6); - - assert.equal(3, imageData.width); - assert.equal(6, imageData.height); - assert.equal(3 * 6 * 4, imageData.data.length); - - assert.equal(255, 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[4]); - assert.equal(255, imageData.data[5]); - assert.equal(0, imageData.data[6]); - assert.equal(255, imageData.data[7]); - - assert.equal(0, imageData.data[8]); - assert.equal(0, imageData.data[9]); - assert.equal(255, imageData.data[10]); - assert.equal(255, imageData.data[11]); - }); - - it("works, full width, RGB24", function () { - var ctx = createTestCanvas(false, {pixelFormat: "RGB24"}); - var imageData = ctx.getImageData(0,0,3,6); - assert.equal(3, imageData.width); - assert.equal(6, imageData.height); - assert.equal(3 * 6 * 4, imageData.data.length); - - assert.equal(255, 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[4]); - assert.equal(255, imageData.data[5]); - assert.equal(0, imageData.data[6]); - assert.equal(255, imageData.data[7]); - - assert.equal(0, imageData.data[8]); - assert.equal(0, imageData.data[9]); - assert.equal(255, imageData.data[10]); - assert.equal(255, imageData.data[11]); - }); - - it("works, full width, RGB16_565", function () { - var ctx = createTestCanvas(false, {pixelFormat: "RGB16_565"}); - var imageData = ctx.getImageData(0,0,3,6); - assert.equal(3, imageData.width); - assert.equal(6, imageData.height); - assert.equal(3 * 6 * 2, imageData.data.length); - - assert.equal((255 & 0b11111) << 11, imageData.data[0]); - assert.equal((255 & 0b111111) << 5, imageData.data[1]); - assert.equal((255 & 0b11111), imageData.data[2]); - - assert.equal((255 & 0b11111) << 11, imageData.data[3]); - assert.equal((255 & 0b111111) << 5, imageData.data[4]); - assert.equal((255 & 0b11111), imageData.data[5]); - }); - - it("works, full width, A8", function () { - var ctx = createTestCanvas(true, {pixelFormat: "A8"}); - var imageData = ctx.getImageData(0,0,3,6); - assert.equal(3, imageData.width); - assert.equal(6, imageData.height); - assert.equal(3 * 6, imageData.data.length); - - assert.equal(63, imageData.data[0]); - assert.equal(127, imageData.data[1]); - assert.equal(191, imageData.data[2]); - - assert.equal(63, imageData.data[3]); - assert.equal(127, imageData.data[4]); - assert.equal(191, imageData.data[5]); - }); - - it("works, full width, A1"); - - it("works, full width, RGB30"); - - it("works, slice, RGBA32", function () { - var ctx = createTestCanvas(); - var imageData = ctx.getImageData(0,0,2,1); - assert.equal(2, imageData.width); - assert.equal(1, imageData.height); - assert.equal(8, imageData.data.length); - - assert.equal(255, 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[4]); - assert.equal(255, imageData.data[5]); - assert.equal(0, imageData.data[6]); - assert.equal(255, imageData.data[7]); - }); - - it("works, slice, RGB24", function () { - var ctx = createTestCanvas(false, {pixelFormat: "RGB24"}); - var imageData = ctx.getImageData(0,0,2,1); - assert.equal(2, imageData.width); - assert.equal(1, imageData.height); - assert.equal(8, imageData.data.length); - - assert.equal(255, 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[4]); - assert.equal(255, imageData.data[5]); - assert.equal(0, imageData.data[6]); - assert.equal(255, imageData.data[7]); - }); - - it("works, slice, RGB16_565", function () { - var ctx = createTestCanvas(false, {pixelFormat: "RGB16_565"}); - var imageData = ctx.getImageData(0,0,2,1); - assert.equal(2, imageData.width); - assert.equal(1, imageData.height); - assert.equal(2 * 1 * 2, imageData.data.length); - - assert.equal((255 & 0b11111) << 11, imageData.data[0]); - assert.equal((255 & 0b111111) << 5, imageData.data[1]); - }); - - it("works, slice, A8", function () { - var ctx = createTestCanvas(true, {pixelFormat: "A8"}); - var imageData = ctx.getImageData(0,0,2,1); - assert.equal(2, imageData.width); - assert.equal(1, imageData.height); - assert.equal(2 * 1, imageData.data.length); - - assert.equal(63, imageData.data[0]); - assert.equal(127, imageData.data[1]); - }); - - it("works, slice, A1"); - - it("works, slice, RGB30"); - - it("works, assignment", function () { - var ctx = createTestCanvas(); - var data = ctx.getImageData(0,0,5,5).data; - data[0] = 50; - assert.equal(50, data[0]); - data[0] = 280; - assert.equal(255, data[0]); - data[0] = -4444; - assert.equal(0, data[0]); - }); - - it("throws if indexes are invalid", function () { - var ctx = createTestCanvas(); - assert.throws(function () { ctx.getImageData(0, 0, 0, 0); }, /IndexSizeError/); - }); - }); + it('works, full width, RGBA32', function () { + var ctx = createTestCanvas() + var imageData = ctx.getImageData(0, 0, 3, 6) + + assert.equal(3, imageData.width) + assert.equal(6, imageData.height) + assert.equal(3 * 6 * 4, imageData.data.length) + + assert.equal(255, 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[4]) + assert.equal(255, imageData.data[5]) + assert.equal(0, imageData.data[6]) + assert.equal(255, imageData.data[7]) + + assert.equal(0, imageData.data[8]) + assert.equal(0, imageData.data[9]) + assert.equal(255, imageData.data[10]) + assert.equal(255, imageData.data[11]) + }) + + it('works, full width, RGB24', function () { + var ctx = createTestCanvas(false, { pixelFormat: 'RGB24' }) + var imageData = ctx.getImageData(0, 0, 3, 6) + assert.equal(3, imageData.width) + assert.equal(6, imageData.height) + assert.equal(3 * 6 * 4, imageData.data.length) + + assert.equal(255, 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[4]) + assert.equal(255, imageData.data[5]) + assert.equal(0, imageData.data[6]) + assert.equal(255, imageData.data[7]) + + assert.equal(0, imageData.data[8]) + assert.equal(0, imageData.data[9]) + assert.equal(255, imageData.data[10]) + assert.equal(255, imageData.data[11]) + }) + + it('works, full width, RGB16_565', function () { + var ctx = createTestCanvas(false, { pixelFormat: 'RGB16_565' }) + var imageData = ctx.getImageData(0, 0, 3, 6) + assert.equal(3, imageData.width) + assert.equal(6, imageData.height) + assert.equal(3 * 6 * 2, imageData.data.length) + + assert.equal((255 & 0b11111) << 11, imageData.data[0]) + assert.equal((255 & 0b111111) << 5, imageData.data[1]) + assert.equal((255 & 0b11111), imageData.data[2]) + + assert.equal((255 & 0b11111) << 11, imageData.data[3]) + assert.equal((255 & 0b111111) << 5, imageData.data[4]) + assert.equal((255 & 0b11111), imageData.data[5]) + }) + + it('works, full width, A8', function () { + var ctx = createTestCanvas(true, { pixelFormat: 'A8' }) + var imageData = ctx.getImageData(0, 0, 3, 6) + assert.equal(3, imageData.width) + assert.equal(6, imageData.height) + assert.equal(3 * 6, imageData.data.length) + + assert.equal(63, imageData.data[0]) + assert.equal(127, imageData.data[1]) + assert.equal(191, imageData.data[2]) + + assert.equal(63, imageData.data[3]) + assert.equal(127, imageData.data[4]) + assert.equal(191, imageData.data[5]) + }) + + it('works, full width, A1') + + it('works, full width, RGB30') + + it('works, slice, RGBA32', function () { + var ctx = createTestCanvas() + var imageData = ctx.getImageData(0, 0, 2, 1) + assert.equal(2, imageData.width) + assert.equal(1, imageData.height) + assert.equal(8, imageData.data.length) + + assert.equal(255, 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[4]) + assert.equal(255, imageData.data[5]) + assert.equal(0, imageData.data[6]) + assert.equal(255, imageData.data[7]) + }) + + it('works, slice, RGB24', function () { + var ctx = createTestCanvas(false, { pixelFormat: 'RGB24' }) + var imageData = ctx.getImageData(0, 0, 2, 1) + assert.equal(2, imageData.width) + assert.equal(1, imageData.height) + assert.equal(8, imageData.data.length) + + assert.equal(255, 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[4]) + assert.equal(255, imageData.data[5]) + assert.equal(0, imageData.data[6]) + assert.equal(255, imageData.data[7]) + }) + + it('works, slice, RGB16_565', function () { + var ctx = createTestCanvas(false, { pixelFormat: 'RGB16_565' }) + var imageData = ctx.getImageData(0, 0, 2, 1) + assert.equal(2, imageData.width) + assert.equal(1, imageData.height) + assert.equal(2 * 1 * 2, imageData.data.length) + + assert.equal((255 & 0b11111) << 11, imageData.data[0]) + assert.equal((255 & 0b111111) << 5, imageData.data[1]) + }) + + it('works, slice, A8', function () { + var ctx = createTestCanvas(true, { pixelFormat: 'A8' }) + var imageData = ctx.getImageData(0, 0, 2, 1) + assert.equal(2, imageData.width) + assert.equal(1, imageData.height) + assert.equal(2 * 1, imageData.data.length) + + assert.equal(63, imageData.data[0]) + assert.equal(127, imageData.data[1]) + }) + + it('works, slice, A1') + + it('works, slice, RGB30') + + it('works, assignment', function () { + var ctx = createTestCanvas() + var data = ctx.getImageData(0, 0, 5, 5).data + data[0] = 50 + assert.equal(50, data[0]) + data[0] = 280 + assert.equal(255, data[0]) + data[0] = -4444 + assert.equal(0, data[0]) + }) + + it('throws if indexes are invalid', function () { + var ctx = createTestCanvas() + assert.throws(function () { ctx.getImageData(0, 0, 0, 0) }, /IndexSizeError/) + }) + }) 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); - else - assert.strictEqual(byte, 255); - }); + if (index + 1 & 3) { assert.strictEqual(byte, 128) } else { assert.strictEqual(byte, 255) } + }) assert.throws(function () { - ctx.fillStyle = Object.create(null); - }); - }); - -}); + ctx.fillStyle = Object.create(null) + }) + }) +}) diff --git a/test/dommatrix.test.js b/test/dommatrix.test.js index 212ce40a4..0386417b7 100644 --- a/test/dommatrix.test.js +++ b/test/dommatrix.test.js @@ -9,22 +9,22 @@ 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) }) } describe('DOMMatrix', function () { - var Avals = [4,5,1,8, 0,3,6,1, 3,5,0,9, 2,4,6,1] - var Bvals = [1,5,1,0, 0,3,6,1, 3,5,7,2, 2,0,6,1] - var AxB = [7,25,31,22, 20,43,24,58, 37,73,45,94, 28,44,8,71] - var BxA = [23,40,89,15, 20,39,66,16, 21,30,87,14, 22,52,74,17] + var Avals = [4, 5, 1, 8, 0, 3, 6, 1, 3, 5, 0, 9, 2, 4, 6, 1] + var Bvals = [1, 5, 1, 0, 0, 3, 6, 1, 3, 5, 7, 2, 2, 0, 6, 1] + var AxB = [7, 25, 31, 22, 20, 43, 24, 58, 37, 73, 45, 94, 28, 44, 8, 71] + var BxA = [23, 40, 89, 15, 20, 39, 66, 16, 21, 30, 87, 14, 22, 52, 74, 17] describe('constructor, general', function () { it('aliases a,b,c,d,e,f properly', function () { @@ -374,7 +374,7 @@ describe('DOMMatrix', function () { }) describe('skewYSelf', function () {}) - + describe('flipX', function () { it('works', function () { var x = new DOMMatrix() @@ -409,14 +409,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) @@ -437,7 +437,7 @@ describe('DOMMatrix', function () { ]) }) }) - + describe('toFloat64Array', function () { it('works', function () { var x = new DOMMatrix() diff --git a/test/image.test.js b/test/image.test.js index 8e79cae2f..e06642c65 100644 --- a/test/image.test.js +++ b/test/image.test.js @@ -6,7 +6,7 @@ * Module dependencies. */ -const {createCanvas, loadImage} = require('../'); +const { createCanvas, loadImage } = require('../') const Image = require('../').Image const assert = require('assert') @@ -23,12 +23,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) => { @@ -87,7 +87,7 @@ describe('Image', function () { it('loads SVG data URL base64', function () { 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) @@ -99,7 +99,7 @@ describe('Image', function () { it('loads SVG data URL utf8', function () { 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) @@ -154,7 +154,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') done() } img.src = `${__dirname}/fixtures/159-crash1.jpg` @@ -213,7 +213,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 @@ -240,7 +240,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 @@ -297,53 +297,53 @@ 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 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, @@ -353,88 +353,88 @@ 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 + ]) + + done() + } - img.onerror = err => { throw err; }; - img.src = path.join(bmp_dir, 'min.bmp'); - }); + 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('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/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 } } From 9ebb62487988b4660ed844e10e186fac9ead0c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:47:16 +0100 Subject: [PATCH 03/56] Move `getFormat()` implementation out of header file --- src/backend/Backend.cc | 6 ++++++ src/backend/Backend.h | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 0ea3b7af2..28e7fc95d 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -3,6 +3,7 @@ Backend::Backend(string name) : name(name) + , format(CAIRO_FORMAT_INVALID) , width(0) , height(0) , surface(NULL) @@ -10,6 +11,7 @@ Backend::Backend(string name) {} Backend::Backend(string name, int width, int height) : name(name) + , format(CAIRO_FORMAT_INVALID) , width(width) , height(height) , surface(NULL) @@ -86,6 +88,10 @@ void Backend::setHeight(int height_) this->recreateSurface(); } +cairo_format_t Backend::getFormat() +{ + return this->format; +} bool Backend::isSurfaceValid(){ bool hadSurface = surface != NULL; bool isValid = true; diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 5bbc80f4e..31a67d57d 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -21,6 +21,7 @@ class Backend : public Nan::ObjectWrap private: const string name; const char* error = NULL; + cairo_format_t format; protected: int width; @@ -53,9 +54,7 @@ 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(); bool isSurfaceValid(); inline const char* getError(){ return error; } From b37d64813ce44ad91be05083c6c852668158afa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:47:59 +0100 Subject: [PATCH 04/56] Added `Backend::setFormat()` method --- src/backend/Backend.cc | 6 ++++++ src/backend/Backend.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 28e7fc95d..1379a9339 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -92,6 +92,12 @@ 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 31a67d57d..ddb74c985 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -55,6 +55,7 @@ class Backend : public Nan::ObjectWrap // Overridden by ImageBackend. SVG and PDF thus always return INVALID. virtual cairo_format_t getFormat(); + virtual void setFormat(cairo_format_t format); bool isSurfaceValid(); inline const char* getError(){ return error; } From 2aa765cbc42086ce04c3e1fc3cca8311ea71b394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:49:48 +0100 Subject: [PATCH 05/56] Added `FBDevBackend::setFormat()` method --- src/backend/FBDevBackend.cc | 21 +++++++++++++++++++++ src/backend/FBDevBackend.h | 1 + 2 files changed, 22 insertions(+) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 581be177c..b1cc44829 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -113,6 +113,27 @@ void FBDevBackend::setHeight(int height) Backend::setHeight(width); } +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_ARGB32: fb_vinfo.bits_per_pixel = 32; break; + + default: + throw FBDevBackendException("Only valid formats are RGB16_565 & ARGB32"); + } + + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, + "Error setting variable framebuffer information"); + + Backend::setFormat(format); +} Nan::Persistent FBDevBackend::constructor; diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 55e90e51c..51d51400b 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -17,6 +17,7 @@ cairo_surface_t* createSurface(); void setWidth(int width); void setHeight(int height); + void setFormat(cairo_format_t format); public: FBDevBackend(string deviceName); static Nan::Persistent constructor; From 2e60f78beaa21a1a85e49574b176bc340cae1a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:51:53 +0100 Subject: [PATCH 06/56] [FBDevBackend] Get `width` and `height` in constructor --- src/backend/FBDevBackend.cc | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index b1cc44829..723a0ebb3 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -37,6 +37,14 @@ FBDevBackend::FBDevBackend(string deviceName) if(this->fb_data == MAP_FAILED) throw FBDevBackendException("Failed to map framebuffer device to memory"); + + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + Backend::setWidth(fb_vinfo.xres); + Backend::setHeight(fb_vinfo.yres); } FBDevBackend::~FBDevBackend() @@ -63,9 +71,6 @@ cairo_surface_t* FBDevBackend::createSurface() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); - this->width = fb_vinfo.xres; - this->height = fb_vinfo.yres; - // switch through bpp and decide on which format for the cairo surface to use cairo_format_t format; switch(fb_vinfo.bits_per_pixel) From c29ef89113130e100f203dbd48e4850cb24df5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:52:58 +0100 Subject: [PATCH 07/56] [FBDevBackend] Set `format` in constructor --- src/backend/FBDevBackend.cc | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 723a0ebb3..1c6b19a2c 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -15,6 +15,20 @@ 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 32: return CAIRO_FORMAT_ARGB32; + + default: + throw FBDevBackendException("Only valid formats are RGB16_565 & ARGB32"); + } +} + + FBDevBackend::FBDevBackend(string deviceName) : Backend("fbdev") { @@ -45,6 +59,7 @@ FBDevBackend::FBDevBackend(string deviceName) Backend::setWidth(fb_vinfo.xres); Backend::setHeight(fb_vinfo.yres); + Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); } FBDevBackend::~FBDevBackend() @@ -71,20 +86,10 @@ cairo_surface_t* FBDevBackend::createSurface() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); - // switch through bpp and decide on which format for the cairo surface to use - cairo_format_t format; - switch(fb_vinfo.bits_per_pixel) - { - case 16: format = CAIRO_FORMAT_RGB16_565; break; - case 32: format = CAIRO_FORMAT_ARGB32; break; - - default: - throw FBDevBackendException("Only valid formats are RGB16_565 & ARGB32"); - } - // create cairo surface from data - this->surface = cairo_image_surface_create_for_data(this->fb_data, format, - this->width, this->height, fb_finfo.line_length); + this->surface = cairo_image_surface_create_for_data(this->fb_data, + bits2format(fb_vinfo.bits_per_pixel), fb_vinfo.xres, fb_vinfo.yres, + fb_finfo.line_length); return this->surface; } From 851ffb208602e2596e1fd7e0b828c639a645e0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Tue, 20 Nov 2018 05:54:18 +0100 Subject: [PATCH 08/56] [FBDevBackend][fix] Wrongly copy&pasted variable --- src/backend/FBDevBackend.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 1c6b19a2c..601696464 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -121,7 +121,7 @@ void FBDevBackend::setHeight(int height) this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); - Backend::setHeight(width); + Backend::setHeight(height); } void FBDevBackend::setFormat(cairo_format_t format) { From ad2ddc011398fdfd7f8faa15a1c441f4a256961c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 20:26:59 +0100 Subject: [PATCH 09/56] [FbDev] Set default FbDev device as constant --- examples/simple_fbdev.js | 2 +- src/backend/FBDevBackend.cc | 2 +- src/backend/FBDevBackend.h | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/simple_fbdev.js b/examples/simple_fbdev.js index a1f28df5c..6c8832495 100644 --- a/examples/simple_fbdev.js +++ b/examples/simple_fbdev.js @@ -11,7 +11,7 @@ const { backends: { FBDevBackend }, Canvas } = require('..') const squareSize = 100 -var device = process.argv[2] || '/dev/fb0' +var device = process.argv[2] var backend = new FBDevBackend(device) var canvas = new Canvas(backend) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 601696464..7042572c2 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -161,7 +161,7 @@ void FBDevBackend::Initialize(Handle target) NAN_METHOD(FBDevBackend::New) { - string fbDevice = "/dev/fb0"; + string fbDevice = DEFAULT_DEVICE; if(info[0]->IsString()) fbDevice = *String::Utf8Value(info[0].As()); FBDevBackend* backend = new FBDevBackend(fbDevice); diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 51d51400b..17b6a686f 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -6,7 +6,12 @@ #include #include "Backend.h" using namespace std; - class FBDevBackend : public Backend + + +const string DEFAULT_DEVICE = "/dev/fb0"; + + +class FBDevBackend : public Backend { private: int fb_fd; From 3e4f681a74b14391e1578107735b163f38ce3c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 20:33:22 +0100 Subject: [PATCH 10/56] [FbDev] Move initialization of framebuffer device to `initFbDev()` method --- src/backend/FBDevBackend.cc | 36 +++++++++++++++++++++--------------- src/backend/FBDevBackend.h | 11 +++++++---- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 7042572c2..e82e32a51 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -31,6 +31,26 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(string deviceName) : Backend("fbdev") +{ + struct fb_var_screeninfo fb_vinfo; + + this->initFbDev(deviceName, &fb_vinfo); + + Backend::setWidth(fb_vinfo.xres); + Backend::setHeight(fb_vinfo.yres); + Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); +} + +FBDevBackend::~FBDevBackend() +{ + this->destroySurface(); + + munmap(this->fb_data, this->fb_finfo.smem_len); + 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); @@ -52,22 +72,8 @@ FBDevBackend::FBDevBackend(string deviceName) if(this->fb_data == MAP_FAILED) throw FBDevBackendException("Failed to map framebuffer device to memory"); - struct fb_var_screeninfo fb_vinfo; - - this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, "Error reading variable framebuffer information"); - - Backend::setWidth(fb_vinfo.xres); - Backend::setHeight(fb_vinfo.yres); - Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); -} - -FBDevBackend::~FBDevBackend() -{ - this->destroySurface(); - - munmap(this->fb_data, this->fb_finfo.smem_len); - close(this->fb_fd); } diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 17b6a686f..962d703d0 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -17,10 +17,13 @@ class FBDevBackend : public Backend int fb_fd; struct fb_fix_screeninfo fb_finfo; unsigned char* fb_data; - ~FBDevBackend(); - void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); - cairo_surface_t* createSurface(); - void setWidth(int width); + + ~FBDevBackend(); + + void initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo); + void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); + cairo_surface_t* createSurface(); + void setWidth(int width); void setHeight(int height); void setFormat(cairo_format_t format); public: From cf99d46d7817a92660dce9a2efa71354dfe79c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 20:38:37 +0100 Subject: [PATCH 11/56] [FbDev] Allow to define framebuffer device from `Canvas` constructor --- src/Canvas.cc | 8 +++++++- src/backend/FBDevBackend.cc | 10 ++++++++++ src/backend/FBDevBackend.h | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 549705ad9..58727c5a2 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -94,7 +94,13 @@ NAN_METHOD(Canvas::New) { if (info[1]->IsNumber()) height = Nan::To(info[1]).FromMaybe(0); if (info[2]->IsString()) { - if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) + if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { + if (info[3]->IsString()) + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); + else + backend = new FBDevBackend(width, height); + } + else if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) backend = new PdfBackend(width, height); else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) backend = new SvgBackend(width, height); diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index e82e32a51..65bda45a0 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -29,6 +29,16 @@ cairo_format_t bits2format(__u32 bits_per_pixel) } +FBDevBackend::FBDevBackend(int width, int height, string deviceName) + : Backend("fbdev", width, height) +{ + struct fb_var_screeninfo fb_vinfo; + + this->initFbDev(deviceName, &fb_vinfo); + + Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); +} + FBDevBackend::FBDevBackend(string deviceName) : Backend("fbdev") { diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 962d703d0..3ed436333 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -27,6 +27,7 @@ class FBDevBackend : public Backend void setHeight(int height); void setFormat(cairo_format_t format); public: + FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE); FBDevBackend(string deviceName); static Nan::Persistent constructor; static void Initialize(v8::Handle target); From 5cc863151b64b12ebfcda667ee6838dc1b1cf226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 21:02:39 +0100 Subject: [PATCH 12/56] [FbDev] Don't regenerate surface on creation for non-standard color modes --- src/backend/Backend.h | 2 +- src/backend/FBDevBackend.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index ddb74c985..12debc196 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -21,11 +21,11 @@ class Backend : public Nan::ObjectWrap private: const string name; const char* error = NULL; - cairo_format_t format; protected: int width; int height; + cairo_format_t format; cairo_surface_t* surface; Canvas* canvas; diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 65bda45a0..44dc285ce 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -36,7 +36,6 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName) this->initFbDev(deviceName, &fb_vinfo); - Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); } FBDevBackend::FBDevBackend(string deviceName) @@ -48,7 +47,6 @@ FBDevBackend::FBDevBackend(string deviceName) Backend::setWidth(fb_vinfo.xres); Backend::setHeight(fb_vinfo.yres); - Backend::setFormat(bits2format(fb_vinfo.bits_per_pixel)); } FBDevBackend::~FBDevBackend() @@ -84,6 +82,8 @@ void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vin this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, "Error reading variable framebuffer information"); + + this->format = bits2format(fb_vinfo->bits_per_pixel); } From 9a583f369ea8d47f71964af40d53600c7af8edb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 21:03:43 +0100 Subject: [PATCH 13/56] [FbDev] Don't regenerate surface on creation to define the width and height --- src/backend/FBDevBackend.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 44dc285ce..9a1e725b9 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -45,8 +45,8 @@ FBDevBackend::FBDevBackend(string deviceName) this->initFbDev(deviceName, &fb_vinfo); - Backend::setWidth(fb_vinfo.xres); - Backend::setHeight(fb_vinfo.yres); + this->width = fb_vinfo.xres; + this->height = fb_vinfo.yres; } FBDevBackend::~FBDevBackend() From 58382524c9be869588aab9fa1770d4c2ac09e043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Wed, 28 Nov 2018 21:04:32 +0100 Subject: [PATCH 14/56] [FbDev][fix] Set framebuffer dimensions if specified --- src/backend/FBDevBackend.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 9a1e725b9..450c2bd56 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -36,6 +36,11 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName) 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) From 848ab2e09e5d72550d3ac2c6fa2109fbf4ffede0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 1 Dec 2018 11:32:51 +0100 Subject: [PATCH 15/56] [Backend] Use default arguments to remove duplicated constructor --- src/backend/Backend.cc | 8 -------- src/backend/Backend.h | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 1379a9339..a4991c69c 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -1,14 +1,6 @@ #include "Backend.h" -Backend::Backend(string name) - : name(name) - , format(CAIRO_FORMAT_INVALID) - , width(0) - , height(0) - , surface(NULL) - , canvas(NULL) -{} Backend::Backend(string name, int width, int height) : name(name) , format(CAIRO_FORMAT_INVALID) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 12debc196..c53b9daae 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -29,8 +29,7 @@ class Backend : public Nan::ObjectWrap cairo_surface_t* surface; Canvas* canvas; - Backend(string name); - Backend(string name, int width, int height); + Backend(string name, int width = 0, int height = 0); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } From ffaac6b176fa5170a931b4a9f3b09041926f7a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 6 Dec 2018 19:12:43 +0100 Subject: [PATCH 16/56] [Backend] Add async method `waitVSync()` --- src/backend/Backend.cc | 38 ++++++++++++++++++++++++++++++++++++++ src/backend/Backend.h | 9 +++++++++ 2 files changed, 47 insertions(+) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 34ef541f5..319207179 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -1,6 +1,29 @@ #include "Backend.h" +using Nan::AsyncQueueWorker; +using Nan::AsyncWorker; +using Nan::Callback; + + +class WaitVSync: public AsyncWorker +{ + public: + WaitVSync(Callback* callback, Backend* backend) + : AsyncWorker(callback, "Backend:WaitVSync") + , backend(backend) + {} + + void Execute() + { + backend->waitVSync(); + } + + private: + Backend* backend; +}; + + Backend::Backend(string name, int width, int height) : name(name) , width(width) @@ -97,6 +120,21 @@ bool Backend::isSurfaceValid(){ } +NAN_METHOD(Backend::waitVSync) +{ + Backend* backend = Nan::ObjectWrap::Unwrap(info.This()); + + Callback* callback = new Callback(info[0].As()); + + AsyncQueueWorker(new WaitVSync(callback, backend)); +} + +void Backend::Initialize(Local ctor) +{ + Nan::SetPrototypeMethod(ctor, "waitVSync", waitVSync); +} + + BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend, string operation_name) : backend(backend) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 75ae37aad..034318d35 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -15,13 +15,19 @@ class Canvas; using namespace std; +using namespace v8; + class Backend : public Nan::ObjectWrap { + friend class WaitVSync; + private: const string name; const char* error = NULL; + virtual void waitVSync(){}; + protected: int width; int height; @@ -31,6 +37,7 @@ class Backend : public Nan::ObjectWrap Backend(string name, int width, int height); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } + static void Initialize(Local ctor); public: virtual ~Backend(); @@ -58,6 +65,8 @@ class Backend : public Nan::ObjectWrap bool isSurfaceValid(); inline const char* getError(){ return error; } + + static NAN_METHOD(waitVSync); }; From c2e07fee8be640396265c36dc1a3a159393a3f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 6 Dec 2018 22:16:12 +0100 Subject: [PATCH 17/56] [FbDevBackend] Add support for framebuffer native VSync --- src/backend/FBDevBackend.cc | 12 ++++++++++++ src/backend/FBDevBackend.h | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 450c2bd56..622ecd7a8 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -167,6 +167,15 @@ void FBDevBackend::setFormat(cairo_format_t format) } +void FBDevBackend::waitVSync() +{ + int arg = 0; + + this->FbDevIoctlHelper(FBIO_WAITFORVSYNC, &arg, + "Error waiting for framebuffer VSync"); +} + + Nan::Persistent FBDevBackend::constructor; void FBDevBackend::Initialize(Handle target) @@ -177,6 +186,9 @@ void FBDevBackend::Initialize(Handle target) FBDevBackend::constructor.Reset(ctor); ctor->InstanceTemplate()->SetInternalFieldCount(1); ctor->SetClassName(Nan::New("FBDevBackend").ToLocalChecked()); + + Backend::Initialize(ctor); + target->Set(Nan::New("FBDevBackend").ToLocalChecked(), ctor->GetFunction()); } diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 3ed436333..40d3d8ef3 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -26,7 +26,10 @@ class FBDevBackend : public Backend void setWidth(int width); void setHeight(int height); void setFormat(cairo_format_t format); - public: + + void waitVSync(); + + public: FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE); FBDevBackend(string deviceName); static Nan::Persistent constructor; From b394d31f6743538d5ba5c58cc90d24aee0571fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 6 Dec 2018 22:26:47 +0100 Subject: [PATCH 18/56] [Backend] `swapBuffers()` method --- src/backend/Backend.cc | 13 +++++++++++++ src/backend/Backend.h | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 34ef541f5..639015c82 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -97,6 +97,19 @@ bool Backend::isSurfaceValid(){ } +NAN_METHOD(Backend::swapBuffers) +{ + Backend* backend = Nan::ObjectWrap::Unwrap(info.This()); + + backend->swapBuffers(); +} + +void Backend::Initialize(Local ctor) +{ + Nan::SetPrototypeMethod(ctor, "swapBuffers", swapBuffers); +} + + BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend, string operation_name) : backend(backend) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 75ae37aad..4f193b229 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -15,6 +15,7 @@ class Canvas; using namespace std; +using namespace v8; class Backend : public Nan::ObjectWrap { @@ -22,6 +23,8 @@ class Backend : public Nan::ObjectWrap const string name; const char* error = NULL; + virtual void swapBuffers(){}; + protected: int width; int height; @@ -31,6 +34,7 @@ class Backend : public Nan::ObjectWrap Backend(string name, int width, int height); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } + static void Initialize(Local ctor); public: virtual ~Backend(); @@ -58,6 +62,8 @@ class Backend : public Nan::ObjectWrap bool isSurfaceValid(); inline const char* getError(){ return error; } + + static NAN_METHOD(swapBuffers); }; From 3618b40279c9b205a6dd735cd4f7037dad2bf513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 7 Dec 2018 13:21:16 +0100 Subject: [PATCH 19/56] [FbDevBackend] Support for optional double buffering (disabled by default) --- src/backend/FBDevBackend.cc | 87 +++++++++++++++++++++++++++++++++++-- src/backend/FBDevBackend.h | 17 +++++++- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 622ecd7a8..e4419d990 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -29,8 +29,11 @@ cairo_format_t bits2format(__u32 bits_per_pixel) } -FBDevBackend::FBDevBackend(int width, int height, string deviceName) +FBDevBackend::FBDevBackend(int width, int height, string deviceName, + bool useDoubleBuffer, bool forceUseCopyBuffer) : Backend("fbdev", width, height) + , useDoubleBuffer(useDoubleBuffer) + , useCopyBackBuffer(false) { struct fb_var_screeninfo fb_vinfo; @@ -41,10 +44,15 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName) this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); + + this->enableDoubleBuffer(&fb_vinfo, forceUseCopyBuffer); } -FBDevBackend::FBDevBackend(string deviceName) +FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, + bool forceUseCopyBuffer) : Backend("fbdev") + , useDoubleBuffer(useDoubleBuffer) + , useCopyBackBuffer(false) { struct fb_var_screeninfo fb_vinfo; @@ -52,12 +60,16 @@ FBDevBackend::FBDevBackend(string deviceName) this->width = fb_vinfo.xres; this->height = fb_vinfo.yres; + + this->enableDoubleBuffer(&fb_vinfo, forceUseCopyBuffer); } FBDevBackend::~FBDevBackend() { this->destroySurface(); + if(useCopyBackBuffer) free(back_buffer); + munmap(this->fb_data, this->fb_finfo.smem_len); close(this->fb_fd); } @@ -91,6 +103,36 @@ void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vin this->format = bits2format(fb_vinfo->bits_per_pixel); } +void FBDevBackend::enableDoubleBuffer(struct fb_var_screeninfo* fb_vinfo, + bool forceUseCopyBuffer) +{ + front_buffer = this->fb_data; + + if(!useDoubleBuffer) + back_buffer = this->fb_data; + + else + { + if(forceUseCopyBuffer) + useCopyBackBuffer = true; + + else + { + fb_vinfo->yres_virtual = height * 2; + + // Try to use real double buffer 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. + useCopyBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, fb_vinfo) == -1; + } + + back_buffer = useCopyBackBuffer + ? (unsigned char*)malloc(this->fb_finfo.smem_len) + : this->fb_data + fb_vinfo->yres * fb_finfo.line_length; + } +} void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, string errmsg) @@ -108,7 +150,7 @@ cairo_surface_t* FBDevBackend::createSurface() "Error reading variable framebuffer information"); // create cairo surface from data - this->surface = cairo_image_surface_create_for_data(this->fb_data, + this->surface = cairo_image_surface_create_for_data(this->back_buffer, bits2format(fb_vinfo.bits_per_pixel), fb_vinfo.xres, fb_vinfo.yres, fb_finfo.line_length); @@ -139,6 +181,8 @@ void FBDevBackend::setHeight(int height) fb_vinfo.yres = height; + if(useCopyBackBuffer) fb_vinfo.yres_virtual = height * 2; + this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); @@ -167,6 +211,43 @@ void FBDevBackend::setFormat(cairo_format_t format) } +void FBDevBackend::copyBackBuffer() +{ + memcpy(front_buffer, back_buffer, this->fb_finfo.smem_len); +} +void FBDevBackend::flipBuffers() +{ + // Update display panning + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + + 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 to force to create a new one in the new back buffer + destroySurface(); +} + +void FBDevBackend::swapBuffers() +{ + if(!useDoubleBuffer) return; + + if(useCopyBackBuffer) + copyBackBuffer(); + + else + flipBuffers(); +} + void FBDevBackend::waitVSync() { int arg = 0; diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 40d3d8ef3..9100f8892 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -17,21 +17,34 @@ class FBDevBackend : public Backend int fb_fd; struct fb_fix_screeninfo fb_finfo; unsigned char* fb_data; + unsigned char* front_buffer; + unsigned char* back_buffer; + bool useDoubleBuffer; + bool useCopyBackBuffer; ~FBDevBackend(); void initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo); + void enableDoubleBuffer(struct fb_var_screeninfo* fb_vinfo, + bool forceUseCopyBuffer); void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); + cairo_surface_t* createSurface(); + void setWidth(int width); void setHeight(int height); void setFormat(cairo_format_t format); + void copyBackBuffer(); + void flipBuffers(); + void swapBuffers(); void waitVSync(); public: - FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE); - FBDevBackend(string deviceName); + FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE, + bool useDoubleBuffer = false, bool forceUseCopyBuffer = false); + FBDevBackend(string deviceName, bool useDoubleBuffer = false, + bool forceUseCopyBuffer = false); static Nan::Persistent constructor; static void Initialize(v8::Handle target); static NAN_METHOD(New); From 995a3f324c9a9c7831de2736086204a775dc467b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 7 Dec 2018 13:22:07 +0100 Subject: [PATCH 20/56] [FbDevBackend] Allow to enable double buffering from Javascript --- src/Canvas.cc | 14 ++++++++++++-- src/backend/FBDevBackend.cc | 9 ++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 58727c5a2..bc22461e0 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -95,8 +95,18 @@ NAN_METHOD(Canvas::New) { if (info[2]->IsString()) { if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { - if (info[3]->IsString()) - backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); + if (info[3]->IsString()) { + if(info[4]->IsBoolean()) { + if(info[5]->IsBoolean()) + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + info[4]->BooleanValue(), info[5]->BooleanValue()); + else + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + info[4]->BooleanValue()); + } + else + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); + } else backend = new FBDevBackend(width, height); } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index e4419d990..336ddde83 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -278,7 +278,14 @@ NAN_METHOD(FBDevBackend::New) string fbDevice = DEFAULT_DEVICE; if(info[0]->IsString()) fbDevice = *String::Utf8Value(info[0].As()); - FBDevBackend* backend = new FBDevBackend(fbDevice); + bool useDoubleBuffer = false; + if(info[1]->IsBoolean()) useDoubleBuffer = info[1]->BooleanValue(); + + bool forceUseCopyBuffer = false; + if(info[2]->IsBoolean()) forceUseCopyBuffer = info[2]->BooleanValue(); + + FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer, + forceUseCopyBuffer); backend->Wrap(info.This()); info.GetReturnValue().Set(info.This()); From 0e8be2246aa27db45e2e41b60ef9f6210db61152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 7 Dec 2018 20:35:03 +0100 Subject: [PATCH 21/56] clean-up --- src/backend/FBDevBackend.h | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 3ed436333..73abcb919 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -1,11 +1,16 @@ #ifndef __FBDEV_BACKEND_H__ #define __FBDEV_BACKEND_H__ - #include + +#include #include - #include - #include - #include "Backend.h" - using namespace std; + +#include +#include + +#include "Backend.h" + + +using namespace std; const string DEFAULT_DEVICE = "/dev/fb0"; @@ -29,17 +34,23 @@ class FBDevBackend : public Backend public: FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE); FBDevBackend(string deviceName); - static Nan::Persistent constructor; + + static Nan::Persistent constructor; static void Initialize(v8::Handle target); static NAN_METHOD(New); }; - class FBDevBackendException : public std::exception + + +class FBDevBackendException : public std::exception { private: string err_msg; - public: + + public: FBDevBackendException(const string msg) : err_msg(msg) {}; ~FBDevBackendException() throw() {}; - const char *what() const throw() { return this->err_msg.c_str(); }; + + const char *what() const throw() { return this->err_msg.c_str(); }; }; - #endif + +#endif From 81fa69f90b94770706ed4a7af406f995e78a2c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 7 Dec 2018 20:41:40 +0100 Subject: [PATCH 22/56] [FbDev] Show used pixel format in `simple_fbdev` example --- examples/simple_fbdev.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/simple_fbdev.js b/examples/simple_fbdev.js index 6c8832495..f8b440322 100644 --- a/examples/simple_fbdev.js +++ b/examples/simple_fbdev.js @@ -32,7 +32,8 @@ ctx.fillRect(0, offsetY, squareSize, squareSize) ctx.fillStyle = '#FFFFFF' ctx.fillRect(offsetX, offsetY, squareSize, squareSize) -console.log('Width: ' + canvas.width + ', Height: ' + canvas.height) +console.log('Width: ' + canvas.width + ', Height: ' + canvas.height + + 'Pixel format: ' + ctx.pixelFormat) var outPath = join(__dirname, 'rectangle.png') From b79795d638cc5de2d0f920a9c1cfb04a2f25bd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 8 Dec 2018 19:29:04 +0100 Subject: [PATCH 23/56] [Backend] Made `destroySurface()` method virtual --- src/backend/Backend.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 0f5d8337a..f6a943db3 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -47,11 +47,10 @@ class Backend : public Nan::ObjectWrap void setCanvas(Canvas* canvas); virtual cairo_surface_t* createSurface() = 0; + virtual void destroySurface(); virtual cairo_surface_t* recreateSurface(); DLL_PUBLIC cairo_surface_t* getSurface(); - void destroySurface(); - DLL_PUBLIC string getName(); DLL_PUBLIC int getWidth(); From 0778ed89f9af63b17e9cbe88309002960ea6e746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 8 Dec 2018 19:32:17 +0100 Subject: [PATCH 24/56] [FbDevBackend] Made `copyBackBuffer()` copy only screen size memory --- src/backend/FBDevBackend.cc | 23 ++++++++++++----------- src/backend/FBDevBackend.h | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 336ddde83..357fc16e6 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -211,21 +211,17 @@ void FBDevBackend::setFormat(cairo_format_t format) } -void FBDevBackend::copyBackBuffer() +void FBDevBackend::copyBackBuffer(struct fb_var_screeninfo* fb_vinfo) { - memcpy(front_buffer, back_buffer, this->fb_finfo.smem_len); + memcpy(front_buffer, back_buffer, fb_vinfo->yres * fb_finfo.line_length); } -void FBDevBackend::flipBuffers() +void FBDevBackend::flipBuffers(struct fb_var_screeninfo* fb_vinfo) { // Update display panning - struct fb_var_screeninfo fb_vinfo; + fb_vinfo->yoffset = fb_vinfo->yoffset ? 0 : fb_vinfo->yres; - this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, - "Error reading variable framebuffer information"); - fb_vinfo.yoffset = fb_vinfo.yoffset ? 0 : fb_vinfo.yres; - - this->FbDevIoctlHelper(FBIOPAN_DISPLAY, &fb_vinfo, + this->FbDevIoctlHelper(FBIOPAN_DISPLAY, fb_vinfo, "Error panning the framebuffer display"); // Swap front and back buffers pointers @@ -241,11 +237,16 @@ void FBDevBackend::swapBuffers() { if(!useDoubleBuffer) return; + struct fb_var_screeninfo fb_vinfo; + + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, + "Error reading variable framebuffer information"); + if(useCopyBackBuffer) - copyBackBuffer(); + copyBackBuffer(&fb_vinfo); else - flipBuffers(); + flipBuffers(&fb_vinfo); } void FBDevBackend::waitVSync() diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 9100f8892..b89241f7d 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -35,8 +35,8 @@ class FBDevBackend : public Backend void setHeight(int height); void setFormat(cairo_format_t format); - void copyBackBuffer(); - void flipBuffers(); + void copyBackBuffer(struct fb_var_screeninfo* fb_vinfo); + void flipBuffers(struct fb_var_screeninfo* fb_vinfo); void swapBuffers(); void waitVSync(); From 9f183c3fca55a5df817f9b21356e3990ea6d7d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 8 Dec 2018 19:33:33 +0100 Subject: [PATCH 25/56] [FbDevBackend] Add `destroySurface()` that free in-memory buffer --- src/backend/FBDevBackend.cc | 7 +++++++ src/backend/FBDevBackend.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 357fc16e6..52ad1e9ef 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -157,6 +157,13 @@ cairo_surface_t* FBDevBackend::createSurface() return this->surface; } +void FBDevBackend::destroySurface() +{ + if(useInMemoryBackBuffer) free(back_buffer); + + Backend::destroySurface(); +} + void FBDevBackend::setWidth(int width) { diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index b89241f7d..ea87e040e 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -30,6 +30,7 @@ class FBDevBackend : public Backend void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); cairo_surface_t* createSurface(); + void destroySurface(); void setWidth(int width); void setHeight(int height); From 73b4b4d478a40d05bacac960b548c776de42ee54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 8 Dec 2018 19:37:38 +0100 Subject: [PATCH 26/56] [FbDevBackend] Replaced `forceUseCopyBuffer` for lazy checking of panning --- src/Canvas.cc | 11 +--- src/backend/FBDevBackend.cc | 106 +++++++++++++++++++----------------- src/backend/FBDevBackend.h | 8 +-- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index bc22461e0..da5cc48e8 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -96,14 +96,9 @@ NAN_METHOD(Canvas::New) { if (info[2]->IsString()) { 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]), - info[4]->BooleanValue(), info[5]->BooleanValue()); - else - backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), - info[4]->BooleanValue()); - } + if(info[4]->IsBoolean()) + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + info[4]->BooleanValue()); else backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 52ad1e9ef..93e3f8404 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -30,9 +30,10 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(int width, int height, string deviceName, - bool useDoubleBuffer, bool forceUseCopyBuffer) + bool useDoubleBuffer) : Backend("fbdev", width, height) , useDoubleBuffer(useDoubleBuffer) + , useInMemoryBackBuffer(false) , useCopyBackBuffer(false) { struct fb_var_screeninfo fb_vinfo; @@ -44,14 +45,12 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); - - this->enableDoubleBuffer(&fb_vinfo, forceUseCopyBuffer); } -FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, - bool forceUseCopyBuffer) +FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer) : Backend("fbdev") , useDoubleBuffer(useDoubleBuffer) + , useInMemoryBackBuffer(false) , useCopyBackBuffer(false) { struct fb_var_screeninfo fb_vinfo; @@ -60,16 +59,12 @@ FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, this->width = fb_vinfo.xres; this->height = fb_vinfo.yres; - - this->enableDoubleBuffer(&fb_vinfo, forceUseCopyBuffer); } FBDevBackend::~FBDevBackend() { this->destroySurface(); - if(useCopyBackBuffer) free(back_buffer); - munmap(this->fb_data, this->fb_finfo.smem_len); close(this->fb_fd); } @@ -97,42 +92,14 @@ void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vin if(this->fb_data == MAP_FAILED) throw FBDevBackendException("Failed to map framebuffer device to memory"); + front_buffer = back_buffer = this->fb_data; + this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, "Error reading variable framebuffer information"); this->format = bits2format(fb_vinfo->bits_per_pixel); } -void FBDevBackend::enableDoubleBuffer(struct fb_var_screeninfo* fb_vinfo, - bool forceUseCopyBuffer) -{ - front_buffer = this->fb_data; - - if(!useDoubleBuffer) - back_buffer = this->fb_data; - - else - { - if(forceUseCopyBuffer) - useCopyBackBuffer = true; - - else - { - fb_vinfo->yres_virtual = height * 2; - - // Try to use real double buffer 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. - useCopyBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, fb_vinfo) == -1; - } - - back_buffer = useCopyBackBuffer - ? (unsigned char*)malloc(this->fb_finfo.smem_len) - : this->fb_data + fb_vinfo->yres * fb_finfo.line_length; - } -} void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, string errmsg) @@ -149,6 +116,47 @@ cairo_surface_t* FBDevBackend::createSurface() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); + if(useDoubleBuffer) + { + front_buffer = this->fb_data; + + // 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 = height * 2; + + useInMemoryBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, &fb_vinfo) == -1; + + if(useInMemoryBackBuffer) + { + useCopyBackBuffer = true; + + back_buffer = (unsigned char*)malloc(fb_vinfo.yres * fb_finfo.line_length); + } + + else + { + // back buffer in memory card + back_buffer = this->fb_data + fb_vinfo.yres * fb_finfo.line_length; + + // 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; + + useCopyBackBuffer = ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; + + if(!useCopyBackBuffer) + { + 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, bits2format(fb_vinfo.bits_per_pixel), fb_vinfo.xres, fb_vinfo.yres, @@ -188,8 +196,6 @@ void FBDevBackend::setHeight(int height) fb_vinfo.yres = height; - if(useCopyBackBuffer) fb_vinfo.yres_virtual = height * 2; - this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); @@ -227,7 +233,6 @@ void FBDevBackend::flipBuffers(struct fb_var_screeninfo* fb_vinfo) // Update display panning fb_vinfo->yoffset = fb_vinfo->yoffset ? 0 : fb_vinfo->yres; - this->FbDevIoctlHelper(FBIOPAN_DISPLAY, fb_vinfo, "Error panning the framebuffer display"); @@ -236,8 +241,15 @@ void FBDevBackend::flipBuffers(struct fb_var_screeninfo* fb_vinfo) front_buffer = back_buffer; back_buffer = aux; - // Destroy Cairo surface to force to create a new one in the new back buffer - destroySurface(); + // Destroy Cairo surface and create it in the new back buffer vertical offset + if(this->surface) + { + cairo_surface_destroy(this->surface); + + this->surface = cairo_image_surface_create_for_data(this->back_buffer, + bits2format(fb_vinfo->bits_per_pixel), fb_vinfo->xres, fb_vinfo->yres, + fb_finfo.line_length); + } } void FBDevBackend::swapBuffers() @@ -289,11 +301,7 @@ NAN_METHOD(FBDevBackend::New) bool useDoubleBuffer = false; if(info[1]->IsBoolean()) useDoubleBuffer = info[1]->BooleanValue(); - bool forceUseCopyBuffer = false; - if(info[2]->IsBoolean()) forceUseCopyBuffer = info[2]->BooleanValue(); - - FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer, - forceUseCopyBuffer); + FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer); backend->Wrap(info.This()); info.GetReturnValue().Set(info.This()); diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index ea87e040e..62da4224f 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -20,13 +20,12 @@ class FBDevBackend : public Backend unsigned char* front_buffer; unsigned char* back_buffer; bool useDoubleBuffer; + bool useInMemoryBackBuffer; bool useCopyBackBuffer; ~FBDevBackend(); void initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo); - void enableDoubleBuffer(struct fb_var_screeninfo* fb_vinfo, - bool forceUseCopyBuffer); void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); cairo_surface_t* createSurface(); @@ -43,9 +42,8 @@ class FBDevBackend : public Backend public: FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE, - bool useDoubleBuffer = false, bool forceUseCopyBuffer = false); - FBDevBackend(string deviceName, bool useDoubleBuffer = false, - bool forceUseCopyBuffer = false); + bool useDoubleBuffer = false); + FBDevBackend(string deviceName, bool useDoubleBuffer = false); static Nan::Persistent constructor; static void Initialize(v8::Handle target); static NAN_METHOD(New); From 6864325e117461a98962a827e9e080dae870267d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Mon, 10 Dec 2018 12:11:45 +0100 Subject: [PATCH 27/56] [FbDevBackend] Added support for 24 bits framebuffers --- src/backend/FBDevBackend.cc | 62 +++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 93e3f8404..89b04ba1b 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -21,10 +22,11 @@ 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 & ARGB32"); + throw FBDevBackendException("Only valid formats are RGB16_565, RGB24 and ARGB32"); } } @@ -92,8 +94,6 @@ void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vin if(this->fb_data == MAP_FAILED) throw FBDevBackendException("Failed to map framebuffer device to memory"); - front_buffer = back_buffer = this->fb_data; - this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, "Error reading variable framebuffer information"); @@ -116,10 +116,13 @@ cairo_surface_t* FBDevBackend::createSurface() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); - if(useDoubleBuffer) - { - front_buffer = this->fb_data; + front_buffer = this->fb_data; + if(!useDoubleBuffer && fb_vinfo.bits_per_pixel != 24) + back_buffer = front_buffer; + + else + { // 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 @@ -127,13 +130,19 @@ cairo_surface_t* FBDevBackend::createSurface() // not see screen redraws. fb_vinfo.yres_virtual = height * 2; + // Adjust virtual framebuffer width to hold RGB24 Cairo surface in 24 bits + fb_vinfo.xres_virtual = fb_vinfo.bits_per_pixel != 24 + ? width + : ceil(width*4/3); + useInMemoryBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, &fb_vinfo) == -1; if(useInMemoryBackBuffer) { useCopyBackBuffer = true; - back_buffer = (unsigned char*)malloc(fb_vinfo.yres * fb_finfo.line_length); + int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); + back_buffer = (unsigned char*)malloc(fb_vinfo.yres * stride); } else @@ -147,7 +156,8 @@ cairo_surface_t* FBDevBackend::createSurface() // VSync this should be minimal and at least we'll not see screen redraws. fb_vinfo.yoffset = fb_vinfo.yres; - useCopyBackBuffer = ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; + useCopyBackBuffer = fb_vinfo.bits_per_pixel != 24 + && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; if(!useCopyBackBuffer) { @@ -158,9 +168,9 @@ cairo_surface_t* FBDevBackend::createSurface() } // create cairo surface from data + int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); this->surface = cairo_image_surface_create_for_data(this->back_buffer, - bits2format(fb_vinfo.bits_per_pixel), fb_vinfo.xres, fb_vinfo.yres, - fb_finfo.line_length); + format, fb_vinfo.xres, fb_vinfo.yres, stride); return this->surface; } @@ -211,10 +221,11 @@ void FBDevBackend::setFormat(cairo_format_t format) 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 & ARGB32"); + throw FBDevBackendException("Only valid formats are RGB16_565, RGB24 and ARGB32"); } this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, @@ -226,10 +237,24 @@ void FBDevBackend::setFormat(cairo_format_t format) void FBDevBackend::copyBackBuffer(struct fb_var_screeninfo* fb_vinfo) { - memcpy(front_buffer, back_buffer, fb_vinfo->yres * fb_finfo.line_length); + 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::flipBuffers(struct fb_var_screeninfo* fb_vinfo) +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; @@ -242,18 +267,15 @@ void FBDevBackend::flipBuffers(struct fb_var_screeninfo* fb_vinfo) back_buffer = aux; // Destroy Cairo surface and create it in the new back buffer vertical offset - if(this->surface) - { - cairo_surface_destroy(this->surface); + cairo_surface_destroy(this->surface); - this->surface = cairo_image_surface_create_for_data(this->back_buffer, - bits2format(fb_vinfo->bits_per_pixel), fb_vinfo->xres, fb_vinfo->yres, - fb_finfo.line_length); - } + 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; if(!useDoubleBuffer) return; struct fb_var_screeninfo fb_vinfo; From 6bf6f1e512d6a77da0e9443624cf82ce1f21fbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Mon, 10 Dec 2018 12:12:58 +0100 Subject: [PATCH 28/56] [FbDev] Replaced `useCopyBackBuffer` for `useFlipPages` for readibility --- src/backend/FBDevBackend.cc | 18 +++++++++--------- src/backend/FBDevBackend.h | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 89b04ba1b..2e728cb4b 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -36,7 +36,7 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, : Backend("fbdev", width, height) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) - , useCopyBackBuffer(false) + , useFlipPages(false) { struct fb_var_screeninfo fb_vinfo; @@ -53,7 +53,7 @@ FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer) : Backend("fbdev") , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) - , useCopyBackBuffer(false) + , useFlipPages(false) { struct fb_var_screeninfo fb_vinfo; @@ -139,7 +139,7 @@ cairo_surface_t* FBDevBackend::createSurface() if(useInMemoryBackBuffer) { - useCopyBackBuffer = true; + useFlipPages = false; int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); back_buffer = (unsigned char*)malloc(fb_vinfo.yres * stride); @@ -156,10 +156,10 @@ cairo_surface_t* FBDevBackend::createSurface() // VSync this should be minimal and at least we'll not see screen redraws. fb_vinfo.yoffset = fb_vinfo.yres; - useCopyBackBuffer = fb_vinfo.bits_per_pixel != 24 - && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; + useFlipPages = fb_vinfo.bits_per_pixel != 24 + && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; - if(!useCopyBackBuffer) + if(useFlipPages) { front_buffer = back_buffer; back_buffer = this->fb_data; @@ -283,11 +283,11 @@ void FBDevBackend::swapBuffers() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); - if(useCopyBackBuffer) - copyBackBuffer(&fb_vinfo); + if(useFlipPages) + flipPages(&fb_vinfo); else - flipBuffers(&fb_vinfo); + copyBackBuffer(&fb_vinfo); } void FBDevBackend::waitVSync() diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 62da4224f..dddff3146 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -21,7 +21,7 @@ class FBDevBackend : public Backend unsigned char* back_buffer; bool useDoubleBuffer; bool useInMemoryBackBuffer; - bool useCopyBackBuffer; + bool useFlipPages; ~FBDevBackend(); @@ -36,7 +36,7 @@ class FBDevBackend : public Backend void setFormat(cairo_format_t format); void copyBackBuffer(struct fb_var_screeninfo* fb_vinfo); - void flipBuffers(struct fb_var_screeninfo* fb_vinfo); + void flipPages(struct fb_var_screeninfo* fb_vinfo); void swapBuffers(); void waitVSync(); From 9c974383b9103dec77c7b95fad2abbf4fa790cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Mon, 10 Dec 2018 12:13:47 +0100 Subject: [PATCH 29/56] [FbDevBackend] Use `calloc()` to clean data at in-memory buffer --- src/backend/FBDevBackend.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 2e728cb4b..525db5dbe 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -142,7 +142,7 @@ cairo_surface_t* FBDevBackend::createSurface() useFlipPages = false; int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); - back_buffer = (unsigned char*)malloc(fb_vinfo.yres * stride); + back_buffer = (unsigned char*)calloc(fb_vinfo.yres, stride); } else From 8b8edd20ccac9fc8b68be870224f6c13856c65ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 13 Dec 2018 15:00:40 +0100 Subject: [PATCH 30/56] [Backends] Don't return `cairo_surface_t` objects in `createSurface()` --- src/Canvas.cc | 2 -- src/backend/Backend.cc | 4 ++-- src/backend/Backend.h | 8 +++++--- src/backend/ImageBackend.cc | 8 +++----- src/backend/ImageBackend.h | 4 ++-- src/backend/PdfBackend.cc | 8 +++----- src/backend/PdfBackend.h | 4 ++-- src/backend/SvgBackend.cc | 8 ++++---- src/backend/SvgBackend.h | 4 ++-- 9 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 8b2825a43..dcd5a226a 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -852,8 +852,6 @@ Canvas::resurface(Local canvas) { Nan::HandleScope scope; Local context; - backend()->recreateSurface(); - // Reset context context = canvas->Get(Nan::New("context").ToLocalChecked()); if (!context->IsUndefined()) { diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 34ef541f5..5891b69aa 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -32,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() { diff --git a/src/backend/Backend.h b/src/backend/Backend.h index 75ae37aad..993c9d948 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -29,6 +29,11 @@ class Backend : public Nan::ObjectWrap Canvas* canvas; Backend(string name, int width, int height); + + 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; } @@ -37,9 +42,6 @@ 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(); void destroySurface(); diff --git a/src/backend/ImageBackend.cc b/src/backend/ImageBackend.cc index b9d169af8..bd6cda85f 100644 --- a/src/backend/ImageBackend.cc +++ b/src/backend/ImageBackend.cc @@ -39,17 +39,15 @@ int32_t ImageBackend::approxBytesPerPixel() { } } -cairo_surface_t* ImageBackend::createSurface() +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 this->surface; } -cairo_surface_t* ImageBackend::recreateSurface() +void ImageBackend::recreateSurface() { // Re-surface if (this->surface) { @@ -59,7 +57,7 @@ cairo_surface_t* ImageBackend::recreateSurface() Nan::AdjustExternalMemory(-approxBytesPerPixel() * old_width * old_height); } - return createSurface(); + createSurface(); } cairo_format_t ImageBackend::getFormat() { diff --git a/src/backend/ImageBackend.h b/src/backend/ImageBackend.h index c245565e0..5d2d3c500 100644 --- a/src/backend/ImageBackend.h +++ b/src/backend/ImageBackend.h @@ -10,8 +10,8 @@ using namespace std; class ImageBackend : public Backend { private: - cairo_surface_t* createSurface(); - cairo_surface_t* recreateSurface(); + void createSurface(); + void recreateSurface(); cairo_format_t format = DEFAULT_FORMAT; public: diff --git a/src/backend/PdfBackend.cc b/src/backend/PdfBackend.cc index df409799a..0ba5a61fd 100644 --- a/src/backend/PdfBackend.cc +++ b/src/backend/PdfBackend.cc @@ -25,16 +25,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(toBuffer, _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 2c597a703..1858692f7 100644 --- a/src/backend/PdfBackend.h +++ b/src/backend/PdfBackend.h @@ -11,8 +11,8 @@ using namespace std; 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/SvgBackend.cc b/src/backend/SvgBackend.cc index eaec46e83..afe8c7402 100644 --- a/src/backend/SvgBackend.cc +++ b/src/backend/SvgBackend.cc @@ -25,18 +25,18 @@ Backend *SvgBackend::construct(int width, int height){ return new SvgBackend(width, height); } -cairo_surface_t* SvgBackend::createSurface() { +void SvgBackend::createSurface() { if (!_closure) _closure = new PdfSvgClosure(canvas); + surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, width, height); - return surface; } -cairo_surface_t* SvgBackend::recreateSurface() { +void SvgBackend::recreateSurface() { cairo_surface_finish(surface); delete _closure; cairo_surface_destroy(surface); - return createSurface(); + createSurface(); } diff --git a/src/backend/SvgBackend.h b/src/backend/SvgBackend.h index b703a3b94..7f5e86a9c 100644 --- a/src/backend/SvgBackend.h +++ b/src/backend/SvgBackend.h @@ -11,8 +11,8 @@ using namespace std; class SvgBackend : public Backend { private: - cairo_surface_t* createSurface(); - cairo_surface_t* recreateSurface(); + void createSurface(); + void recreateSurface(); public: PdfSvgClosure* _closure = NULL; From c21b8a5a144626a63a6bc9f0f19016d26a3556cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 13 Dec 2018 15:13:28 +0100 Subject: [PATCH 31/56] [FbDevBackend] Don't return surface in `createSurface()` --- src/backend/FBDevBackend.cc | 4 +--- src/backend/FBDevBackend.h | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 525db5dbe..3011321fc 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -109,7 +109,7 @@ void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, } -cairo_surface_t* FBDevBackend::createSurface() +void FBDevBackend::createSurface() { struct fb_var_screeninfo fb_vinfo; @@ -171,8 +171,6 @@ cairo_surface_t* FBDevBackend::createSurface() int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); this->surface = cairo_image_surface_create_for_data(this->back_buffer, format, fb_vinfo.xres, fb_vinfo.yres, stride); - - return this->surface; } void FBDevBackend::destroySurface() diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index dddff3146..bde65bc8b 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -28,8 +28,8 @@ class FBDevBackend : public Backend void initFbDev(string deviceName, struct fb_var_screeninfo* fb_vinfo); void FbDevIoctlHelper(unsigned long request, void* data, string errmsg); - cairo_surface_t* createSurface(); - void destroySurface(); + void createSurface(); + void destroySurface(); void setWidth(int width); void setHeight(int height); From 1c8cdeab2340c769a99f4009965137f098254930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 13 Dec 2018 15:43:16 +0100 Subject: [PATCH 32/56] [FbDevBackend][fix] Use pages flipping if driver supports vertical panning --- src/backend/FBDevBackend.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 3011321fc..27acb334d 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -157,10 +157,11 @@ void FBDevBackend::createSurface() fb_vinfo.yoffset = fb_vinfo.yres; useFlipPages = fb_vinfo.bits_per_pixel != 24 - && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == -1; + && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == 0; if(useFlipPages) { + // Swap front and back buffers since vertical panning was succesful front_buffer = back_buffer; back_buffer = this->fb_data; } From b50fe3e2a49ebcb56dc8b088eee05f402ff143b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 13 Dec 2018 15:45:01 +0100 Subject: [PATCH 33/56] [FbDevBackend] Prevent double free'ing of back buffer memory --- src/backend/FBDevBackend.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 27acb334d..8e9f3f841 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -34,6 +34,7 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(int width, int height, string deviceName, bool useDoubleBuffer) : Backend("fbdev", width, height) + , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) , useFlipPages(false) @@ -51,6 +52,7 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer) : Backend("fbdev") + , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) , useFlipPages(false) @@ -111,6 +113,8 @@ void FBDevBackend::FbDevIoctlHelper(unsigned long request, void* data, void FBDevBackend::createSurface() { + destroySurface(); + struct fb_var_screeninfo fb_vinfo; this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, @@ -176,9 +180,14 @@ void FBDevBackend::createSurface() void FBDevBackend::destroySurface() { - if(useInMemoryBackBuffer) free(back_buffer); - Backend::destroySurface(); + + if(useInMemoryBackBuffer && back_buffer) + { + free(back_buffer); + + back_buffer = NULL; + } } From 96383b16115a9c685622132b9373fa17b4da3a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Thu, 13 Dec 2018 15:45:44 +0100 Subject: [PATCH 34/56] [FbDevBackend] Clean-up & improved code readibility --- src/Canvas.cc | 8 ++++---- src/backend/FBDevBackend.cc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 40f7fbec5..ca46eea4e 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -284,7 +284,7 @@ static void parsePNGArgs(Local arg, PngClosure& pngargs) { static void parseJPEGArgs(Local arg, JpegClosure& jpegargs) { // "If Type(quality) is not Number, or if quality is outside that range, the - // user agent must use its default quality value, as if the quality argument + // user agent must use its default quality value, as if the quality argument // had not been given." - 4.12.5.5 if (arg->IsObject()) { Local obj = Nan::To(arg).ToLocalChecked(); @@ -463,9 +463,9 @@ NAN_METHOD(Canvas::ToBuffer) { Nan::ThrowError(Canvas::Error(ex)); return; } - + parseJPEGArgs(info[1], *closure); - + // TODO: only one callback fn in closure // TODO what does this comment mean? canvas->Ref(); closure->pfn = new Nan::Callback(info[0].As()); @@ -507,7 +507,7 @@ streamPNG(void *c, const uint8_t *data, unsigned len) { NAN_METHOD(Canvas::StreamPNGSync) { if (!info[0]->IsFunction()) return Nan::ThrowTypeError("callback function required"); - + Canvas *canvas = Nan::ObjectWrap::Unwrap(info.This()); PngClosure closure(canvas); diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 8e9f3f841..081ed8e07 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -152,7 +152,7 @@ void FBDevBackend::createSurface() else { // back buffer in memory card - back_buffer = this->fb_data + fb_vinfo.yres * fb_finfo.line_length; + back_buffer = front_buffer + fb_vinfo.yres * fb_finfo.line_length; // 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 From f07cba4ba1793a9c2c117a3baf36f10d3bf7359d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 14 Dec 2018 21:44:06 +0100 Subject: [PATCH 35/56] [FbDevBackend] `mmap()` graphic memory on `createSurface()` & code clean-up --- src/backend/FBDevBackend.cc | 85 +++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 081ed8e07..31ca074ba 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -34,6 +34,7 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(int width, int height, string deviceName, bool useDoubleBuffer) : Backend("fbdev", width, height) + , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) @@ -52,6 +53,7 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer) : Backend("fbdev") + , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) @@ -69,7 +71,6 @@ FBDevBackend::~FBDevBackend() { this->destroySurface(); - munmap(this->fb_data, this->fb_finfo.smem_len); close(this->fb_fd); } @@ -86,16 +87,6 @@ void FBDevBackend::initFbDev(string deviceName, struct fb_var_screeninfo* fb_vin throw FBDevBackendException(o.str()); } - this->FbDevIoctlHelper(FBIOGET_FSCREENINFO, &this->fb_finfo, - "Error reading fixed framebuffer information"); - - // Map the device to memory - this->fb_data = (unsigned char*) mmap(0, this->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"); - this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, fb_vinfo, "Error reading variable framebuffer information"); @@ -120,13 +111,13 @@ void FBDevBackend::createSurface() this->FbDevIoctlHelper(FBIOGET_VSCREENINFO, &fb_vinfo, "Error reading variable framebuffer information"); - front_buffer = this->fb_data; - - if(!useDoubleBuffer && fb_vinfo.bits_per_pixel != 24) - back_buffer = front_buffer; + int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); - else + // 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 @@ -141,39 +132,52 @@ void FBDevBackend::createSurface() useInMemoryBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, &fb_vinfo) == -1; - if(useInMemoryBackBuffer) + if(!useInMemoryBackBuffer && fb_vinfo.bits_per_pixel != 24) { - useFlipPages = false; - - int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); - back_buffer = (unsigned char*)calloc(fb_vinfo.yres, stride); - } - - else - { - // back buffer in memory card - back_buffer = front_buffer + fb_vinfo.yres * fb_finfo.line_length; - // 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 = fb_vinfo.bits_per_pixel != 24 - && ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == 0; + useFlipPages = ioctl(this->fb_fd, FBIOPAN_DISPLAY, &fb_vinfo) == 0; + } + } - if(useFlipPages) - { - // Swap front and back buffers since vertical panning was succesful - front_buffer = back_buffer; - back_buffer = this->fb_data; - } + // 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 are already swapped) + front_buffer = back_buffer; + back_buffer = this->fb_data; } } // create cairo surface from data - int stride = cairo_format_stride_for_width(format, fb_vinfo.xres); this->surface = cairo_image_surface_create_for_data(this->back_buffer, format, fb_vinfo.xres, fb_vinfo.yres, stride); } @@ -188,6 +192,13 @@ void FBDevBackend::destroySurface() back_buffer = NULL; } + + if(this->fb_data) + { + munmap(this->fb_data, fb_finfo.smem_len); + + this->fb_data = NULL; + } } From b0f81d77c9c8a4b4d64119a66f4970fb4a87ff1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 15 Dec 2018 14:31:39 +0100 Subject: [PATCH 36/56] [FbDevBackend] Option to enable flip buffers (disabled by default) --- src/Canvas.cc | 11 ++++++++--- src/backend/FBDevBackend.cc | 15 +++++++++++---- src/backend/FBDevBackend.h | 6 ++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index ca46eea4e..41344ca27 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -96,9 +96,14 @@ NAN_METHOD(Canvas::New) { if (info[2]->IsString()) { if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { if (info[3]->IsString()) { - if(info[4]->IsBoolean()) - backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), - info[4]->BooleanValue()); + if(info[4]->IsBoolean()) { + if(info[5]->IsBoolean()) + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + info[4]->BooleanValue(), info[5]->BooleanValue()); + else + backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), + info[4]->BooleanValue()); + } else backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 31ca074ba..948855fff 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -32,13 +32,14 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(int width, int height, string deviceName, - bool useDoubleBuffer) + bool useDoubleBuffer, bool enableFlipPages) : Backend("fbdev", width, height) , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) , useFlipPages(false) + , enableFlipPages(enableFlipPages) { struct fb_var_screeninfo fb_vinfo; @@ -51,13 +52,15 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, "Error setting variable framebuffer information"); } -FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer) +FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, + bool enableFlipPages) : Backend("fbdev") , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) , useInMemoryBackBuffer(false) , useFlipPages(false) + , enableFlipPages(enableFlipPages) { struct fb_var_screeninfo fb_vinfo; @@ -132,7 +135,7 @@ void FBDevBackend::createSurface() useInMemoryBackBuffer = ioctl(this->fb_fd, FBIOPUT_VSCREENINFO, &fb_vinfo) == -1; - if(!useInMemoryBackBuffer && fb_vinfo.bits_per_pixel != 24) + 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 @@ -342,7 +345,11 @@ NAN_METHOD(FBDevBackend::New) bool useDoubleBuffer = false; if(info[1]->IsBoolean()) useDoubleBuffer = info[1]->BooleanValue(); - FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer); + bool enableFlipPages = false; + if(info[2]->IsBoolean()) enableFlipPages = info[2]->BooleanValue(); + + 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 index bde65bc8b..58f81833f 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -22,6 +22,7 @@ class FBDevBackend : public Backend bool useDoubleBuffer; bool useInMemoryBackBuffer; bool useFlipPages; + bool enableFlipPages; ~FBDevBackend(); @@ -42,8 +43,9 @@ class FBDevBackend : public Backend public: FBDevBackend(int width, int height, string deviceName = DEFAULT_DEVICE, - bool useDoubleBuffer = false); - FBDevBackend(string deviceName, bool useDoubleBuffer = false); + bool useDoubleBuffer = false, bool enableFlipPages = false); + FBDevBackend(string deviceName, bool useDoubleBuffer = false, + bool enableFlipPages = false); static Nan::Persistent constructor; static void Initialize(v8::Handle target); static NAN_METHOD(New); From e90b95c42de98ee104db2fcd57ca7ae6225df7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 15 Dec 2018 14:32:07 +0100 Subject: [PATCH 37/56] [FbDevBackend][fix] Reset vertical panning before creating surface --- src/backend/FBDevBackend.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 948855fff..2513c938b 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -116,6 +116,10 @@ void FBDevBackend::createSurface() 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) { From d7378423cfa1d784272c28aa210197a6b2b40a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 15 Dec 2018 14:33:52 +0100 Subject: [PATCH 38/56] [FbDevBackend][fix] Use `swapBuffers()` always for 24 bits images --- src/backend/FBDevBackend.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 2513c938b..5039c410b 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -302,13 +302,14 @@ void FBDevBackend::flipPages(struct fb_var_screeninfo* fb_vinfo) void FBDevBackend::swapBuffers() { if(!this->surface) return; - if(!useDoubleBuffer) 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); From a1830670d3e1e1877c7b5e5a61e3f68c1fd5ffd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 15 Dec 2018 14:34:25 +0100 Subject: [PATCH 39/56] [FbDevBackend] Code clean-up, better readibility --- src/backend/FBDevBackend.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 5039c410b..a3498fe40 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -130,12 +130,12 @@ void FBDevBackend::createSurface() // 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 = height * 2; + 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 - ? width - : ceil(width*4/3); + 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; @@ -178,7 +178,7 @@ void FBDevBackend::createSurface() if(useFlipPages) { // Swap front and back buffers since vertical panning checking was - // succesful (so for the graphic card they are already swapped) + // succesful (so for the graphic card they were already swapped) front_buffer = back_buffer; back_buffer = this->fb_data; } From 6a22cb2c118adc991a10a73a3080f62c38e195d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Mon, 24 Dec 2018 17:27:14 +0100 Subject: [PATCH 40/56] Fixed documentation --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index c76a77fb7..2cdbccf65 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.) @@ -463,7 +463,7 @@ See also: ## SVG Output Support -node-canvas can create SVG documents instead of images. The canva type must be set when creating the canvas as follows: +node-canvas can create SVG documents instead of images. The canvas type must be set when creating the canvas as follows: ```js const canvas = createCanvas(200, 500, 'svg') From bfb4f1d469096da8648e16631ca8456e825cb80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 15:00:03 +0100 Subject: [PATCH 41/56] Fixes after merge --- lib/image.js | 17 ++++++----------- src/backend/FBDevBackend.cc | 8 +++++--- src/backend/FBDevBackend.h | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/image.js b/lib/image.js index 15cd6a18e..a09bcc8e0 100644 --- a/lib/image.js +++ b/lib/image.js @@ -17,12 +17,7 @@ const util = require('util') // Lazily loaded simple-get let get; -const proto = Image.prototype -const _getSource = proto.getSource -const _setSource = proto.setSource - -delete proto.getSource -delete proto.setSource +const {GetSource, SetSource} = bindings; Object.defineProperty(Image.prototype, 'src', { /** @@ -88,11 +83,11 @@ Image.prototype[util.inspect.custom || 'inspect'] = function(){ + ']'; }; -function getSource (img) { - return img._originalSource || _getSource.call(img) +function getSource(img){ + return img._originalSource || GetSource.call(img); } -function setSource (img, src, origSrc) { - _setSource.call(img, src) - img._originalSource = origSrc +function setSource(img, src, origSrc){ + SetSource.call(img, src); + img._originalSource = origSrc; } diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 450c2bd56..8117d1165 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -169,7 +169,7 @@ void FBDevBackend::setFormat(cairo_format_t format) Nan::Persistent FBDevBackend::constructor; -void FBDevBackend::Initialize(Handle target) +void FBDevBackend::Initialize(Local target) { Nan::HandleScope scope; @@ -177,13 +177,15 @@ void FBDevBackend::Initialize(Handle target) FBDevBackend::constructor.Reset(ctor); ctor->InstanceTemplate()->SetInternalFieldCount(1); ctor->SetClassName(Nan::New("FBDevBackend").ToLocalChecked()); - target->Set(Nan::New("FBDevBackend").ToLocalChecked(), ctor->GetFunction()); + 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 = *String::Utf8Value(info[0].As()); + if(info[0]->IsString()) fbDevice = *Nan::Utf8String(info[0]); FBDevBackend* backend = new FBDevBackend(fbDevice); diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 73abcb919..902024164 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -36,7 +36,7 @@ class FBDevBackend : public Backend FBDevBackend(string deviceName); static Nan::Persistent constructor; - static void Initialize(v8::Handle target); + static void Initialize(v8::Local target); static NAN_METHOD(New); }; From b5f5dd07bd44d565e42b8ecd70d2118cd2df8c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 15:00:33 +0100 Subject: [PATCH 42/56] Fixed linting --- test/public/tests.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) 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) From 64fb2e53ed9ec900fd7d2ac8529fc585afdbc521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 15:01:33 +0100 Subject: [PATCH 43/56] Isolated `lint` script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fe619b0ea..61697ae78 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "scripts": { "prebenchmark": "node-gyp build", "benchmark": "node benchmarks/run.js", - "pretest": "standard examples/*.js test/server.js test/public/*.js benchmarks/run.js lib/context2d.js util/has_lib.js browser.js index.js && node-gyp build", + "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", From 45021a7bfd8d664ec788ece85084e86e7c77c7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 15:06:34 +0100 Subject: [PATCH 44/56] Throw exception for unknown canvas type instead create `Image` by default --- src/Canvas.cc | 2 +- test/canvas.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 5fd4fa64a..c689c1a2f 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -114,7 +114,7 @@ NAN_METHOD(Canvas::New) { else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) backend = new SvgBackend(width, height); else - backend = new ImageBackend(width, height); + return Nan::ThrowRangeError("Unknown canvas type"); } else backend = new ImageBackend(width, height); diff --git a/test/canvas.test.js b/test/canvas.test.js index 2e1b7cf51..107132814 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -260,8 +260,8 @@ describe('Canvas', function () { 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') + + assert.throws(function () { createCanvas(10, 10, 'hey'); }, /RangeError/); }) it('Canvas#getContext("2d")', function () { From 601a415721de3d9141cac0d9c155da27f8f6d85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 15:06:46 +0100 Subject: [PATCH 45/56] Updated dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 61697ae78..f7c64fbc3 100644 --- a/package.json +++ b/package.json @@ -50,17 +50,17 @@ "types": "types/index.d.ts", "dependencies": { "nan": "^2.14.0", - "node-pre-gyp": "^0.11.0", + "node-pre-gyp": "^0.14.0", "simple-get": "^3.0.3" }, "devDependencies": { "@types/node": "^10.12.18", "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" + "express": "^4.17.1", + "mocha": "^7.1.1", + "pixelmatch": "^5.1.0", + "standard": "^14.3.3" }, "engines": { "node": ">=6" From 3978af3ce68a74e88adb643f2990af18e2c121cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 18:32:45 +0100 Subject: [PATCH 46/56] Moved screen-only `swapBuffers()` method to isolated `ScreenBuffer` class --- binding.gyp | 1 + src/backend/Backend.cc | 13 ------------- src/backend/Backend.h | 5 ----- src/backend/ScreenBackend.cc | 17 +++++++++++++++++ src/backend/ScreenBackend.h | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 src/backend/ScreenBackend.cc create mode 100644 src/backend/ScreenBackend.h diff --git a/binding.gyp b/binding.gyp index 24b08d5a6..95dc8d9d0 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', diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 3c5fc9bec..a78fecd5c 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -95,19 +95,6 @@ bool Backend::isSurfaceValid(){ } -NAN_METHOD(Backend::swapBuffers) -{ - Backend* backend = Nan::ObjectWrap::Unwrap(info.This()); - - backend->swapBuffers(); -} - -void Backend::Initialize(Local ctor) -{ - Nan::SetPrototypeMethod(ctor, "swapBuffers", swapBuffers); -} - - BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend, std::string operation_name) : backend(backend) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index fbb90f9f4..c0c4e1928 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -15,8 +15,6 @@ class Backend : public Nan::ObjectWrap const std::string name; const char* error = NULL; - virtual void swapBuffers(){}; - protected: int width; int height; @@ -26,7 +24,6 @@ class Backend : public Nan::ObjectWrap Backend(std::string name, int width, int height); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } - static void Initialize(Local ctor); public: virtual ~Backend(); @@ -54,8 +51,6 @@ class Backend : public Nan::ObjectWrap bool isSurfaceValid(); inline const char* getError(){ return error; } - - static NAN_METHOD(swapBuffers); }; diff --git a/src/backend/ScreenBackend.cc b/src/backend/ScreenBackend.cc new file mode 100644 index 000000000..e8392ac30 --- /dev/null +++ b/src/backend/ScreenBackend.cc @@ -0,0 +1,17 @@ +#include "ScreenBackend.h" + + +using namespace v8; + + +NAN_METHOD(ScreenBackend::swapBuffers) +{ + ScreenBackend* backend = Nan::ObjectWrap::Unwrap(info.This()); + + backend->swapBuffers(); +} + +void ScreenBackend::Initialize(Local ctor) +{ + Nan::SetPrototypeMethod(ctor, "swapBuffers", swapBuffers); +} diff --git a/src/backend/ScreenBackend.h b/src/backend/ScreenBackend.h new file mode 100644 index 000000000..e4eeffa39 --- /dev/null +++ b/src/backend/ScreenBackend.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Backend.h" + + +class Canvas; + +class ScreenBackend : public Backend +{ + private: + virtual void swapBuffers(){}; + + static NAN_METHOD(swapBuffers); + + protected: + static void Initialize(v8::Local ctor); +}; From 8e2486124a5c3d32e1ce65ef1ad7e678a926509e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 19:02:47 +0100 Subject: [PATCH 47/56] Moved screens-only `waitVSync` method to `ScreenBackend` abstract class --- src/backend/Backend.cc | 38 ------------------------------------ src/backend/Backend.h | 7 ------- src/backend/ScreenBackend.cc | 33 ++++++++++++++++++++++++++++++- src/backend/ScreenBackend.h | 9 +++++++-- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/src/backend/Backend.cc b/src/backend/Backend.cc index 3e1a0df7f..373e5915b 100644 --- a/src/backend/Backend.cc +++ b/src/backend/Backend.cc @@ -2,29 +2,6 @@ #include -using Nan::AsyncQueueWorker; -using Nan::AsyncWorker; -using Nan::Callback; - - -class WaitVSync: public AsyncWorker -{ - public: - WaitVSync(Callback* callback, Backend* backend) - : AsyncWorker(callback, "Backend:WaitVSync") - , backend(backend) - {} - - void Execute() - { - backend->waitVSync(); - } - - private: - Backend* backend; -}; - - Backend::Backend(std::string name, int width, int height) : name(name) , width(width) @@ -119,21 +96,6 @@ bool Backend::isSurfaceValid(){ } -NAN_METHOD(Backend::waitVSync) -{ - Backend* backend = Nan::ObjectWrap::Unwrap(info.This()); - - Callback* callback = new Callback(info[0].As()); - - AsyncQueueWorker(new WaitVSync(callback, backend)); -} - -void Backend::Initialize(Local ctor) -{ - Nan::SetPrototypeMethod(ctor, "waitVSync", waitVSync); -} - - BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend, std::string operation_name) : backend(backend) diff --git a/src/backend/Backend.h b/src/backend/Backend.h index a75bd28b4..c0c4e1928 100644 --- a/src/backend/Backend.h +++ b/src/backend/Backend.h @@ -11,14 +11,10 @@ class Canvas; class Backend : public Nan::ObjectWrap { - friend class WaitVSync; - private: const std::string name; const char* error = NULL; - virtual void waitVSync(){}; - protected: int width; int height; @@ -28,7 +24,6 @@ class Backend : public Nan::ObjectWrap Backend(std::string name, int width, int height); static void init(const Nan::FunctionCallbackInfo &info); static Backend *construct(int width, int height){ return nullptr; } - static void Initialize(Local ctor); public: virtual ~Backend(); @@ -56,8 +51,6 @@ class Backend : public Nan::ObjectWrap bool isSurfaceValid(); inline const char* getError(){ return error; } - - static NAN_METHOD(waitVSync); }; diff --git a/src/backend/ScreenBackend.cc b/src/backend/ScreenBackend.cc index e8392ac30..b1b70f089 100644 --- a/src/backend/ScreenBackend.cc +++ b/src/backend/ScreenBackend.cc @@ -1,7 +1,27 @@ #include "ScreenBackend.h" -using namespace v8; +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; +}; NAN_METHOD(ScreenBackend::swapBuffers) @@ -11,7 +31,18 @@ NAN_METHOD(ScreenBackend::swapBuffers) 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 index e4eeffa39..ec37c041a 100644 --- a/src/backend/ScreenBackend.h +++ b/src/backend/ScreenBackend.h @@ -3,15 +3,20 @@ #include "Backend.h" -class Canvas; +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: - static void Initialize(v8::Local ctor); + static void Initialize(Local ctor); }; From aa3a6aea8dc3ca4e7c6200c60c361a9a4be9a779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 19:35:43 +0100 Subject: [PATCH 48/56] Fixes after merge --- src/Canvas.cc | 4 ++-- src/backend/FBDevBackend.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 060f12a7e..cfc7d420d 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -108,10 +108,10 @@ NAN_METHOD(Canvas::New) { if(info[4]->IsBoolean()) { if(info[5]->IsBoolean()) backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), - info[4]->BooleanValue(), info[5]->BooleanValue()); + Nan::To(info[4]).FromMaybe(0), Nan::To(info[5]).FromMaybe(0)); else backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3]), - info[4]->BooleanValue()); + Nan::To(info[4]).FromMaybe(0)); } else backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 7c8b09972..826eee31a 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -350,10 +350,10 @@ NAN_METHOD(FBDevBackend::New) if(info[0]->IsString()) fbDevice = *Nan::Utf8String(info[0]); bool useDoubleBuffer = false; - if(info[1]->IsBoolean()) useDoubleBuffer = info[1]->BooleanValue(); + if(info[1]->IsBoolean()) useDoubleBuffer = Nan::To(info[1]).FromMaybe(0); bool enableFlipPages = false; - if(info[2]->IsBoolean()) enableFlipPages = info[2]->BooleanValue(); + if(info[2]->IsBoolean()) enableFlipPages = Nan::To(info[2]).FromMaybe(0); FBDevBackend* backend = new FBDevBackend(fbDevice, useDoubleBuffer, enableFlipPages); From 3023b363d87b4f0192d711cccb0c9774891e745f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 21 Mar 2020 19:46:42 +0100 Subject: [PATCH 49/56] Fixes after merge --- src/backend/FBDevBackend.cc | 14 +++++++------- src/backend/FBDevBackend.h | 4 ++-- src/backend/ScreenBackend.cc | 5 +++++ src/backend/ScreenBackend.h | 2 ++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/backend/FBDevBackend.cc b/src/backend/FBDevBackend.cc index 826eee31a..32b3f69ee 100644 --- a/src/backend/FBDevBackend.cc +++ b/src/backend/FBDevBackend.cc @@ -33,7 +33,7 @@ cairo_format_t bits2format(__u32 bits_per_pixel) FBDevBackend::FBDevBackend(int width, int height, string deviceName, bool useDoubleBuffer, bool enableFlipPages) - : Backend("fbdev", width, height) + : ScreenBackend("fbdev", width, height) , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) @@ -54,7 +54,7 @@ FBDevBackend::FBDevBackend(int width, int height, string deviceName, FBDevBackend::FBDevBackend(string deviceName, bool useDoubleBuffer, bool enableFlipPages) - : Backend("fbdev") + : ScreenBackend("fbdev") , fb_data(NULL) , back_buffer(NULL) , useDoubleBuffer(useDoubleBuffer) @@ -191,7 +191,7 @@ void FBDevBackend::createSurface() void FBDevBackend::destroySurface() { - Backend::destroySurface(); + ScreenBackend::destroySurface(); if(useInMemoryBackBuffer && back_buffer) { @@ -221,7 +221,7 @@ void FBDevBackend::setWidth(int width) this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); - Backend::setWidth(width); + ScreenBackend::setWidth(width); } void FBDevBackend::setHeight(int height) { @@ -235,7 +235,7 @@ void FBDevBackend::setHeight(int height) this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); - Backend::setHeight(height); + ScreenBackend::setHeight(height); } void FBDevBackend::setFormat(cairo_format_t format) { @@ -257,7 +257,7 @@ void FBDevBackend::setFormat(cairo_format_t format) this->FbDevIoctlHelper(FBIOPUT_VSCREENINFO, &fb_vinfo, "Error setting variable framebuffer information"); - Backend::setFormat(format); + ScreenBackend::setFormat(format); } @@ -337,7 +337,7 @@ void FBDevBackend::Initialize(Local target) ctor->InstanceTemplate()->SetInternalFieldCount(1); ctor->SetClassName(Nan::New("FBDevBackend").ToLocalChecked()); - Backend::Initialize(ctor); + ScreenBackend::Initialize(ctor); Nan::Set(target, Nan::New("FBDevBackend").ToLocalChecked(), diff --git a/src/backend/FBDevBackend.h b/src/backend/FBDevBackend.h index 73cf43ce9..8f0a07db6 100644 --- a/src/backend/FBDevBackend.h +++ b/src/backend/FBDevBackend.h @@ -7,7 +7,7 @@ #include #include -#include "Backend.h" +#include "ScreenBackend.h" using namespace std; @@ -16,7 +16,7 @@ using namespace std; const string DEFAULT_DEVICE = "/dev/fb0"; -class FBDevBackend : public Backend +class FBDevBackend : public ScreenBackend { private: int fb_fd; diff --git a/src/backend/ScreenBackend.cc b/src/backend/ScreenBackend.cc index b1b70f089..00ff35a3e 100644 --- a/src/backend/ScreenBackend.cc +++ b/src/backend/ScreenBackend.cc @@ -24,6 +24,11 @@ class WaitVSync: public AsyncWorker }; +ScreenBackend::ScreenBackend(std::string name, int width, int height) + : Backend(name, width, height) +{} + + NAN_METHOD(ScreenBackend::swapBuffers) { ScreenBackend* backend = Nan::ObjectWrap::Unwrap(info.This()); diff --git a/src/backend/ScreenBackend.h b/src/backend/ScreenBackend.h index ec37c041a..36ec32bea 100644 --- a/src/backend/ScreenBackend.h +++ b/src/backend/ScreenBackend.h @@ -18,5 +18,7 @@ class ScreenBackend : public Backend static NAN_METHOD(waitVSync); protected: + ScreenBackend(std::string name, int width = 0, int height = 0); + static void Initialize(Local ctor); }; From fdf14bcafa2346e70109873c0aeec9edec0defb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro?= Date: Sun, 22 Mar 2020 20:57:57 +0100 Subject: [PATCH 50/56] Remove unmaintained Node.js v6 and v8 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d9975ab93..10b3db9ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ node_js: - '13' - '12' - '10' - - '8' - - '6' addons: apt: sources: From 3f38854fc0cce1086fad998807dae1dd9732a31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro?= Date: Sun, 22 Mar 2020 20:58:35 +0100 Subject: [PATCH 51/56] Remove unmaintained Node.js v6 and v8 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d9975ab93..10b3db9ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,6 @@ node_js: - '13' - '12' - '10' - - '8' - - '6' addons: apt: sources: From 173a8e48bf18ecb0538a9f61a425e510e4c9f581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 17 Oct 2020 19:24:29 +0200 Subject: [PATCH 52/56] Update `devDependencies` to fix linting errors --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 78b31f82b..cde80c41f 100644 --- a/package.json +++ b/package.json @@ -53,13 +53,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", + "dtslint": "^4.0.4", "express": "^4.16.3", "mocha": "^5.2.0", "pixelmatch": "^4.0.2", - "standard": "^12.0.1" + "standard": "^12.0.1", + "typescript": "^4.0.3" }, "engines": { "node": ">=6" From 0688eff0549b22cbf524a8cfa1f5c6149918b823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 17 Oct 2020 19:30:45 +0200 Subject: [PATCH 53/56] Update `devDependencies` to fix linting errors # Conflicts: # package.json --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5b71db4e4..8f5c17d09 100644 --- a/package.json +++ b/package.json @@ -54,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", + "dtslint": "^4.0.4", "express": "^4.17.1", "mocha": "^7.1.1", "pixelmatch": "^5.1.0", - "standard": "^14.3.3" + "standard": "^14.3.3", + "typescript": "^4.0.3" }, "engines": { "node": ">=6" From 144337da1f37fe8e2685fa57aab2ba3fef51ca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 17 Oct 2020 19:30:45 +0200 Subject: [PATCH 54/56] Update `devDependencies` to fix linting errors # Conflicts: # package.json --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5b71db4e4..8f5c17d09 100644 --- a/package.json +++ b/package.json @@ -54,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", + "dtslint": "^4.0.4", "express": "^4.17.1", "mocha": "^7.1.1", "pixelmatch": "^5.1.0", - "standard": "^14.3.3" + "standard": "^14.3.3", + "typescript": "^4.0.3" }, "engines": { "node": ">=6" From 6f7876a04fda2c16debe40cf7acb083e6bd76142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 17 Oct 2020 21:17:37 +0200 Subject: [PATCH 55/56] Enable FbDev backend only on Linux --- binding.gyp | 2 +- src/Canvas.cc | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/binding.gyp b/binding.gyp index bc26603f2..65777141e 100644 --- a/binding.gyp +++ b/binding.gyp @@ -145,7 +145,7 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' } }], - ['has_FBDev=="true"', + ['OS=="linux" and has_FBDev=="true"', { 'defines': ['HAS_FBDEV'], 'sources': ['src/backend/FBDevBackend.cc'] diff --git a/src/Canvas.cc b/src/Canvas.cc index b84baa8f3..8015c3061 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -26,11 +26,14 @@ #include "JPEGStream.h" #endif -#include "backend/FBDevBackend.h" #include "backend/ImageBackend.h" #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) " \ @@ -103,16 +106,18 @@ NAN_METHOD(Canvas::New) { if (info[1]->IsNumber()) height = Nan::To(info[1]).FromMaybe(0); if (info[2]->IsString()) { - if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { + if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) + 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()) backend = new FBDevBackend(width, height, *Nan::Utf8String(info[3])); else backend = new FBDevBackend(width, height); } - else if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) - backend = new PdfBackend(width, height); - else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) - backend = new SvgBackend(width, height); +#endif else return Nan::ThrowRangeError("Unknown canvas type"); } @@ -120,10 +125,13 @@ NAN_METHOD(Canvas::New) { backend = new ImageBackend(width, height); } else if (info[0]->IsObject()) { - if (Nan::New(FBDevBackend::constructor)->HasInstance(info[0]) || - Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || + 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"); From 81904df526b2de52b2fab176882cd72109ce62d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Sat, 17 Oct 2020 21:24:00 +0200 Subject: [PATCH 56/56] Enable FbDev backend only on Linux # Conflicts: # src/Canvas.cc --- binding.gyp | 2 +- src/Canvas.cc | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/binding.gyp b/binding.gyp index 40fe4814b..841e59ee2 100644 --- a/binding.gyp +++ b/binding.gyp @@ -146,7 +146,7 @@ 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES' } }], - ['has_FBDev=="true"', + ['OS=="linux" and has_FBDev=="true"', { 'defines': ['HAS_FBDEV'], 'sources': ['src/backend/FBDevBackend.cc'] diff --git a/src/Canvas.cc b/src/Canvas.cc index 3b9b2883c..1924be138 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -26,11 +26,14 @@ #include "JPEGStream.h" #endif -#include "backend/FBDevBackend.h" #include "backend/ImageBackend.h" #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) " \ @@ -103,7 +106,12 @@ NAN_METHOD(Canvas::New) { if (info[1]->IsNumber()) height = Nan::To(info[1]).FromMaybe(0); if (info[2]->IsString()) { - if (0 == strcmp("fbdev", *Nan::Utf8String(info[2]))) { + if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) + 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()) @@ -119,10 +127,7 @@ NAN_METHOD(Canvas::New) { else backend = new FBDevBackend(width, height); } - else if (0 == strcmp("pdf", *Nan::Utf8String(info[2]))) - backend = new PdfBackend(width, height); - else if (0 == strcmp("svg", *Nan::Utf8String(info[2]))) - backend = new SvgBackend(width, height); +#endif else return Nan::ThrowRangeError("Unknown canvas type"); } @@ -130,10 +135,13 @@ NAN_METHOD(Canvas::New) { backend = new ImageBackend(width, height); } else if (info[0]->IsObject()) { - if (Nan::New(FBDevBackend::constructor)->HasInstance(info[0]) || - Nan::New(ImageBackend::constructor)->HasInstance(info[0]) || + 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");