Browse Source

Initial commit

v1.x
Tj Holowaychuk 15 years ago
commit
3a9d122456
  1. 7
      .gitignore
  2. 0
      History.md
  3. 6
      Makefile
  4. 42
      Readme.md
  5. 127
      lib/canvas.js
  6. 152
      lib/colors.js
  7. 11
      package.json
  8. 76
      src/canvas.cc
  9. 33
      src/canvas.h
  10. 331
      src/context2d.cc
  11. 41
      src/context2d.h
  12. 16
      src/node-canvas.cc
  13. 197
      test/canvas.test.js
  14. 19
      wscript

7
.gitignore

@ -0,0 +1,7 @@
build
.DS_Store
test.js
.lock-wscript
test.png
test.html
test/*.png

0
History.md

6
Makefile

@ -0,0 +1,6 @@
test:
@./support/expresso/bin/expresso \
-I lib
.PHONY: test

42
Readme.md

@ -0,0 +1,42 @@
# node-canvas
## TODO
[ ] drawing shapes
[ ] line styles
[ ] fix colors
[ ] gradients
[ ] Image
[ ] patterns
[ ] text
[ ] rectangles
[ ] transformations
[ ] compositing
[ ] PixelArray
[ ] async saving to disk (png, svg, etc)
## License
(The MIT License)
Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

127
lib/canvas.js

@ -0,0 +1,127 @@
/*!
* Canvas
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var canvas = require('../build/default/canvas')
, colors = require('./colors')
, Canvas = canvas.Canvas
, Context2d = canvas.Context2d;
/**
* Export `Canvas` as the module.
*/
var Canvas = exports = module.exports = Canvas;
/**
* Library version.
*/
exports.version = '0.0.1';
/**
* Parse the given color `str`.
*
* Current supports:
*
* - #nnn
* - #nnnnnn
* - rgb(r,g,b)
* - rgba(r,g,b,a)
* - color
*
* Examples
*
* - #fff
* - #FFF
* - #FFFFFF
* - rgb(255,255,5)
* - rgba(255,255,5,.8)
* - rgba(255,255,5,0.8)
* - white
* - red
*
* @param {String} str
* @return {Array}
* @api public
*/
exports.parseColor = function(str){
str = colors[str] || String(str);
if (0 == str.indexOf('rgba')) {
var captures = /rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.\d+|\.\d+|\d+) *\)/.exec(str);
if (!captures) return;
return [
parseInt(captures[1], 10)
, parseInt(captures[2], 10)
, parseInt(captures[3], 10)
, parseFloat(captures[4], 10)
];
} else if (0 == str.indexOf('rgb')) {
var captures = /rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *\)/.exec(str);
if (!captures) return;
return [
parseInt(captures[1], 10)
, parseInt(captures[2], 10)
, parseInt(captures[3], 10)
, 1
];
} else if ('#' == str.charAt(0) && str.length > 4) {
var captures = /#(\w{2})(\w{2})(\w{2})/.exec(str);
if (!captures) return;
return [
parseInt(captures[1], 16)
, parseInt(captures[2], 16)
, parseInt(captures[3], 16)
, 1
];
} else if ('#' == str.charAt(0)) {
var captures = /#(\w)(\w)(\w)/.exec(str);
if (!captures) return;
return [
parseInt(captures[1] + captures[1], 16)
, parseInt(captures[2] + captures[2], 16)
, parseInt(captures[3] + captures[3], 16)
, 1
];
}
};
/**
* Get a context object.
*
* @param {String} contextId
* @return {Context2d}
* @api public
*/
Canvas.prototype.getContext = function(contextId){
if ('2d' == contextId) {
var ctx = new Context2d(this);
this.context = ctx;
ctx.canvas = this;
return ctx;
}
};
Context2d.prototype.__defineSetter__('fillStyle', function(val){
var rgba = exports.parseColor(val) || [0,0,0,1];
this.lastFillStyle = rgba;
this.setFillRGBA(
rgba[0]
, rgba[1]
, rgba[2]
, rgba[3]);
});
Context2d.prototype.__defineGetter__('fillStyle', function(){
var rgba = this.lastFillStyle;
return 'rgba(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ',' + rgba[3] + ')';
});

152
lib/colors.js

@ -0,0 +1,152 @@
/*!
* Canvas - colors
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
module.exports = {
aliceblue: '#f0f8ff'
, antiquewhite: '#faebd7'
, aqua: '#00ffff'
, aquamarine: '#7fffd4'
, azure: '#f0ffff'
, beige: '#f5f5dc'
, bisque: '#ffe4c4'
, black: '#000000'
, blanchedalmond: '#ffebcd'
, blue: '#0000ff'
, blueviolet: '#8a2be2'
, brown: '#a52a2a'
, burlywood: '#deb887'
, cadetblue: '#5f9ea0'
, chartreuse: '#7fff00'
, chocolate: '#d2691e'
, coral: '#ff7f50'
, cornflowerblue: '#6495ed'
, cornsilk: '#fff8dc'
, crimson: '#dc143c'
, cyan: '#00ffff'
, darkblue: '#00008b'
, darkcyan: '#008b8b'
, darkgoldenrod: '#b8860b'
, darkgray: '#a9a9a9'
, darkgreen: '#006400'
, darkkhaki: '#bdb76b'
, darkmagenta: '#8b008b'
, darkolivegreen: '#556b2f'
, darkorange: '#ff8c00'
, darkorchid: '#9932cc'
, darkred: '#8b0000'
, darksalmon: '#e9967a'
, darkseagreen: '#8fbc8f'
, darkslateblue: '#483d8b'
, darkslategray: '#2f4f4f'
, darkturquoise: '#00ced1'
, darkviolet: '#9400d3'
, deeppink: '#ff1493'
, deepskyblue: '#00bfff'
, dimgray: '#696969'
, dodgerblue: '#1e90ff'
, feldspar: '#d19275'
, firebrick: '#b22222'
, floralwhite: '#fffaf0'
, forestgreen: '#228b22'
, fuchsia: '#ff00ff'
, gainsboro: '#dcdcdc'
, ghostwhite: '#f8f8ff'
, gold: '#ffd700'
, goldenrod: '#daa520'
, gray: '#808080'
, green: '#008000'
, greenyellow: '#adff2f'
, honeydew: '#f0fff0'
, hotpink: '#ff69b4'
, indianred : '#cd5c5c'
, indigo : '#4b0082'
, ivory: '#fffff0'
, khaki: '#f0e68c'
, lavender: '#e6e6fa'
, lavenderblush: '#fff0f5'
, lawngreen: '#7cfc00'
, lemonchiffon: '#fffacd'
, lightblue: '#add8e6'
, lightcoral: '#f08080'
, lightcyan: '#e0ffff'
, lightgoldenrodyellow: '#fafad2'
, lightgrey: '#d3d3d3'
, lightgreen: '#90ee90'
, lightpink: '#ffb6c1'
, lightsalmon: '#ffa07a'
, lightseagreen: '#20b2aa'
, lightskyblue: '#87cefa'
, lightslateblue: '#8470ff'
, lightslategray: '#778899'
, lightsteelblue: '#b0c4de'
, lightyellow: '#ffffe0'
, lime: '#00ff00'
, limegreen: '#32cd32'
, linen: '#faf0e6'
, magenta: '#ff00ff'
, maroon: '#800000'
, mediumaquamarine: '#66cdaa'
, mediumblue: '#0000cd'
, mediumorchid: '#ba55d3'
, mediumpurple: '#9370d8'
, mediumseagreen: '#3cb371'
, mediumslateblue: '#7b68ee'
, mediumspringgreen: '#00fa9a'
, mediumturquoise: '#48d1cc'
, mediumvioletred: '#c71585'
, midnightblue: '#191970'
, mintcream: '#f5fffa'
, mistyrose: '#ffe4e1'
, moccasin: '#ffe4b5'
, navajowhite: '#ffdead'
, navy: '#000080'
, oldlace: '#fdf5e6'
, olive: '#808000'
, olivedrab: '#6b8e23'
, orange: '#ffa500'
, orangered: '#ff4500'
, orchid: '#da70d6'
, palegoldenrod: '#eee8aa'
, palegreen: '#98fb98'
, paleturquoise: '#afeeee'
, palevioletred: '#d87093'
, papayawhip: '#ffefd5'
, peachpuff: '#ffdab9'
, peru: '#cd853f'
, pink: '#ffc0cb'
, plum: '#dda0dd'
, powderblue: '#b0e0e6'
, purple: '#800080'
, red: '#ff0000'
, rosybrown: '#bc8f8f'
, royalblue: '#4169e1'
, saddlebrown: '#8b4513'
, salmon: '#fa8072'
, sandybrown: '#f4a460'
, seagreen: '#2e8b57'
, seashell: '#fff5ee'
, sienna: '#a0522d'
, silver: '#c0c0c0'
, skyblue: '#87ceeb'
, slateblue: '#6a5acd'
, slategray: '#708090'
, snow: '#fffafa'
, springgreen: '#00ff7f'
, steelblue: '#4682b4'
, tan: '#d2b48c'
, teal: '#008080'
, thistle: '#d8bfd8'
, tomato: '#ff6347'
, turquoise: '#40e0d0'
, violet: '#ee82ee'
, violetred: '#d02090'
, wheat: '#f5deb3'
, white: '#ffffff'
, whitesmoke: '#f5f5f5'
, yellow: '#ffff00'
, yellowgreen: '#9acd32'
};

11
package.json

@ -0,0 +1,11 @@
{ "name": "canvas"
, "description": "node canvas implementation backed by Cairo"
, "version": "0.0.1"
, "author": "TJ Holowaychuk <tj@vision-media.ca>"
, "keywords": []
, "main": "./lib/canvas.js"
, "scripts": {
"preinstall": "node-waf configure build"
}
, "engines": { "node": ">= 0.2.0" }
}

76
src/canvas.cc

@ -0,0 +1,76 @@
//
// canvas.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "canvas.h"
using namespace v8;
/*
* Initialize Canvas.
*/
void
Canvas::Initialize(Handle<Object> target) {
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(Canvas::New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(String::NewSymbol("Canvas"));
NODE_SET_PROTOTYPE_METHOD(t, "savePNG", SavePNG);
target->Set(String::NewSymbol("Canvas"), t->GetFunction());
}
/*
* Initialize a Canvas with the given width and height.
*/
Handle<Value>
Canvas::New(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("width required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("height required")));
Canvas *canvas = new Canvas(args[0]->Uint32Value(), args[1]->Uint32Value());
canvas->Wrap(args.This());
return args.This();
}
/*
* Initialize cairo surface.
*/
Canvas::Canvas(int width, int height): ObjectWrap() {
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
}
/*
* Destroy cairo surface.
*/
Canvas::~Canvas() {
cairo_surface_destroy(_surface);
}
/*
* Save a PNG at the given path.
*/
Handle<Value>
Canvas::SavePNG(const Arguments &args) {
HandleScope scope;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
if (!args[0]->IsString())
return ThrowException(Exception::TypeError(String::New("path required")));
String::Utf8Value path(args[0]->ToString());
cairo_surface_write_to_png(canvas->getSurface(), *path);
return Undefined();
}

33
src/canvas.h

@ -0,0 +1,33 @@
//
// canvas.h
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#ifndef __NODE_CANVAS_H__
#define __NODE_CANVAS_H__
#include <v8.h>
#include <node.h>
#include <node_object_wrap.h>
#include <cairo.h>
using namespace v8;
class Canvas: public node::ObjectWrap {
public:
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments &args);
static Handle<Value> SavePNG(const Arguments &args);
inline cairo_surface_t *getSurface(){ return _surface; }
protected:
Canvas(int width, int height);
private:
~Canvas();
cairo_surface_t *_surface;
};
#endif

331
src/context2d.cc

@ -0,0 +1,331 @@
//
// context2d.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "canvas.h"
#include "context2d.h"
#include <math.h>
using namespace v8;
using namespace node;
/*
* Rectangle arg assertions.
*/
#define RECT_ARGS \
if (!args[0]->IsNumber()) \
return ThrowException(Exception::TypeError(String::New("x required"))); \
if (!args[1]->IsNumber()) \
return ThrowException(Exception::TypeError(String::New("y required"))); \
if (!args[2]->IsNumber()) \
return ThrowException(Exception::TypeError(String::New("width required"))); \
if (!args[3]->IsNumber()) \
return ThrowException(Exception::TypeError(String::New("height required"))); \
int x = args[0]->Int32Value(); \
int y = args[1]->Int32Value(); \
int width = args[2]->Int32Value(); \
int height = args[3]->Int32Value();
/*
* Initialize Context2d.
*/
void
Context2d::Initialize(Handle<Object> target) {
HandleScope scope;
// Constructor
Local<FunctionTemplate> t = FunctionTemplate::New(Context2d::New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(String::NewSymbol("Context2d"));
// Prototype
NODE_SET_PROTOTYPE_METHOD(t, "fill", Fill);
NODE_SET_PROTOTYPE_METHOD(t, "stroke", Stroke);
NODE_SET_PROTOTYPE_METHOD(t, "fillRect", FillRect);
NODE_SET_PROTOTYPE_METHOD(t, "strokeRect", StrokeRect);
NODE_SET_PROTOTYPE_METHOD(t, "clearRect", ClearRect);
NODE_SET_PROTOTYPE_METHOD(t, "moveTo", MoveTo);
NODE_SET_PROTOTYPE_METHOD(t, "lineTo", LineTo);
NODE_SET_PROTOTYPE_METHOD(t, "bezierCurveTo", BezierCurveTo);
NODE_SET_PROTOTYPE_METHOD(t, "beginPath", BeginPath);
NODE_SET_PROTOTYPE_METHOD(t, "closePath", ClosePath);
NODE_SET_PROTOTYPE_METHOD(t, "arc", Arc);
NODE_SET_PROTOTYPE_METHOD(t, "setFillRGBA", SetFillRGBA);
target->Set(String::NewSymbol("Context2d"), t->GetFunction());
}
/*
* Initialize a new Context2d with the given canvas.
*/
Handle<Value>
Context2d::New(const Arguments &args) {
HandleScope scope;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args[0]->ToObject());
Context2d *context = new Context2d(canvas);
context->Wrap(args.This());
return args.This();
}
/*
* Create a cairo context.
*/
Context2d::Context2d(Canvas *canvas): ObjectWrap() {
_canvas = canvas;
_context = cairo_create(canvas->getSurface());
cairo_set_source_rgba(_context, 0, 0, 0, 1);
}
/*
* Destroy cairo context.
*/
Context2d::~Context2d() {
cairo_destroy(_context);
}
/*
* Set fill RGBA, use internally for fillStyle=
*/
Handle<Value>
Context2d::SetFillRGBA(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("r required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("g required")));
if (!args[2]->IsNumber())
return ThrowException(Exception::TypeError(String::New("b required")));
if (!args[3]->IsNumber())
return ThrowException(Exception::TypeError(String::New("alpha required")));
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_set_source_rgba(context->getContext()
, args[0]->Int32Value()
, args[1]->Int32Value()
, args[2]->Int32Value()
, args[3]->NumberValue());
return Undefined();
}
/*
* Bezier curve.
*/
Handle<Value>
Context2d::BezierCurveTo(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("cp1x required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("cp1y required")));
if (!args[2]->IsNumber())
return ThrowException(Exception::TypeError(String::New("cp2x required")));
if (!args[3]->IsNumber())
return ThrowException(Exception::TypeError(String::New("cp2y required")));
if (!args[4]->IsNumber())
return ThrowException(Exception::TypeError(String::New("x required")));
if (!args[5]->IsNumber())
return ThrowException(Exception::TypeError(String::New("y required")));
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_curve_to(context->getContext()
, args[0]->NumberValue()
, args[1]->NumberValue()
, args[2]->NumberValue()
, args[3]->NumberValue()
, args[4]->NumberValue()
, args[5]->NumberValue());
return Undefined();
}
/*
* Creates a new subpath.
*/
Handle<Value>
Context2d::BeginPath(const Arguments &args) {
HandleScope scope;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_new_path(context->getContext());
return Undefined();
}
/*
* Marks the subpath as closed.
*/
Handle<Value>
Context2d::ClosePath(const Arguments &args) {
HandleScope scope;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_close_path(context->getContext());
return Undefined();
}
/*
* Fill the shape.
*/
Handle<Value>
Context2d::Fill(const Arguments &args) {
HandleScope scope;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_fill(context->getContext());
return Undefined();
}
/*
* Stroke the shape.
*/
Handle<Value>
Context2d::Stroke(const Arguments &args) {
HandleScope scope;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_stroke(context->getContext());
return Undefined();
}
/*
* Adds a point to the current subpath.
*/
Handle<Value>
Context2d::LineTo(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("x required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("y required")));
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_line_to(context->getContext()
, args[0]->Int32Value()
, args[1]->Int32Value());
return Undefined();
}
/*
* Creates a new subpath at the given point.
*/
Handle<Value>
Context2d::MoveTo(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("x required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("y required")));
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_move_to(context->getContext()
, args[0]->Int32Value()
, args[1]->Int32Value());
return Undefined();
}
/*
* Fill the rectangle defined by x, y, with and height.
*/
Handle<Value>
Context2d::FillRect(const Arguments &args) {
HandleScope scope;
RECT_ARGS;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_t *ctx = context->getContext();
cairo_rectangle(ctx, x, y, width, height);
cairo_fill(ctx);
return Undefined();
}
/*
* Stroke the rectangle defined by x, y, width and height.
*/
Handle<Value>
Context2d::StrokeRect(const Arguments &args) {
HandleScope scope;
RECT_ARGS;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_t *ctx = context->getContext();
cairo_rectangle(ctx, x, y, width, height);
cairo_stroke(ctx);
return Undefined();
}
/*
* Clears all pixels defined by x, y, width and height.
*/
Handle<Value>
Context2d::ClearRect(const Arguments &args) {
HandleScope scope;
RECT_ARGS;
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_t *ctx = context->getContext();
cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(ctx, x, y, width, height);
cairo_fill(ctx);
cairo_set_operator(ctx, CAIRO_OPERATOR_OVER);
return Undefined();
}
/*
* Adds an arc at x, y with the given radis and start/end angles.
*/
Handle<Value>
Context2d::Arc(const Arguments &args) {
HandleScope scope;
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("x required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("y required")));
if (!args[2]->IsNumber())
return ThrowException(Exception::TypeError(String::New("radius required")));
if (!args[3]->IsNumber())
return ThrowException(Exception::TypeError(String::New("startAngle required")));
if (!args[4]->IsNumber())
return ThrowException(Exception::TypeError(String::New("endAngle required")));
bool anticlockwise = args[5]->BooleanValue();
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
cairo_t *ctx = context->getContext();
if (anticlockwise && M_PI * 2 != args[4]->NumberValue()) {
cairo_arc_negative(ctx
, args[0]->NumberValue()
, args[1]->NumberValue()
, args[2]->NumberValue()
, args[3]->NumberValue()
, args[4]->NumberValue());
} else {
cairo_arc(ctx
, args[0]->NumberValue()
, args[1]->NumberValue()
, args[2]->NumberValue()
, args[3]->NumberValue()
, args[4]->NumberValue());
}
return Undefined();
}

41
src/context2d.h

@ -0,0 +1,41 @@
//
// context2d.h
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#ifndef __NODE_CONTEXT2D_H__
#define __NODE_CONTEXT2D_H__
#include "canvas.h"
class Context2d: public node::ObjectWrap {
public:
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments &args);
static Handle<Value> BeginPath(const Arguments &args);
static Handle<Value> ClosePath(const Arguments &args);
static Handle<Value> Fill(const Arguments &args);
static Handle<Value> Stroke(const Arguments &args);
static Handle<Value> SetFillRGBA(const Arguments &args);
static Handle<Value> BezierCurveTo(const Arguments &args);
static Handle<Value> LineTo(const Arguments &args);
static Handle<Value> MoveTo(const Arguments &args);
static Handle<Value> FillRect(const Arguments &args);
static Handle<Value> StrokeRect(const Arguments &args);
static Handle<Value> ClearRect(const Arguments &args);
static Handle<Value> Arc(const Arguments &args);
inline cairo_t *getContext(){ return _context; }
inline Canvas *getCanvas(){ return _canvas; }
protected:
Context2d(Canvas *canvas);
private:
~Context2d();
Canvas *_canvas;
cairo_t *_context;
};
#endif

16
src/node-canvas.cc

@ -0,0 +1,16 @@
//
// node-canvas.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "canvas.h"
#include "context2d.h"
extern "C" void
init (Handle<Object> target) {
HandleScope scope;
Canvas::Initialize(target);
Context2d::Initialize(target);
}

197
test/canvas.test.js

@ -0,0 +1,197 @@
/**
* Module dependencies.
*/
var Canvas = require('canvas')
, assert = require('assert')
, crypto = require('crypto')
, fs = require('fs');
function hash(val) {
return crypto.createHash('md5').update(val).digest('hex');
}
function assertChecksum(canvas, path, checksum, msg) {
canvas.savePNG(path);
fs.readFile(path, function(err, buf){
assert.equal(hash(buf), checksum, msg);
});
}
module.exports = {
'test .version': function(assert){
assert.match(Canvas.version, /^\d+\.\d+\.\d+$/);
},
'test .parseColor()': function(assert){
assert.equal(null, Canvas.parseColor());
assert.equal(null, Canvas.parseColor(''));
// rgb()
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255,165,0)'));
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255, 165, 0)'));
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255 , 165 , 0)'));
assert.equal(null, Canvas.parseColor('rgb()'));
// rgba()
assert.eql([255,165,0,1], Canvas.parseColor('rgba(255,165,0,1)'));
assert.eql([255,165,0,1], Canvas.parseColor('rgba(255,165,0,1)'));
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165,0,0.6)'));
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165, 0, 0.6)'));
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165 , 0 ,.6)'));
assert.equal(null, Canvas.parseColor('rgba(2554,165 , 0 ,.6)'));
assert.equal(null, Canvas.parseColor('rgba()'));
// hex
assert.eql([165,89,89,1], Canvas.parseColor('#A55959'));
assert.eql([255,255,255,1], Canvas.parseColor('#FFFFFF'));
assert.eql([255,255,255,1], Canvas.parseColor('#ffffff'));
assert.eql([255,255,255,1], Canvas.parseColor('#FFF'));
assert.eql([255,255,255,1], Canvas.parseColor('#fff'));
// name
assert.eql([255,255,255,1], Canvas.parseColor('white'));
assert.eql([0,0,0,1], Canvas.parseColor('black'));
},
'test Canvas#getContext("2d")': function(assert){
var canvas = new Canvas(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');
},
'test Canvas#getContext("invalid")': function(assert){
assert.equal(null, new Canvas(200, 300).getContext('invalid'));
},
'test Canvas#clearRect()': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/clearRect.png';
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.fillRect(50,50,50,50);
assertChecksum(
canvas
, path
, 'e21404e97142a76c0c8d14cf0fab400f'
, 'Canvas#clearRect() failed');
},
'test Canvas#strokeRect()': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/strokeRect.png';
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
assertChecksum(
canvas
, path
, '1cd349f7d3d2ae5a2bce13ca35dcaa94'
, 'Canvas#strokeRect() failed');
},
'test Canvas#lineTo()': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/lineTo.png';
// Filled triangle
ctx.beginPath();
ctx.moveTo(25.5,25);
ctx.lineTo(105,25);
ctx.lineTo(25,105);
ctx.fill();
// Stroked triangle
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke();
assertChecksum(
canvas
, path
, '44cce447dcb15918c9baf9170f87911f'
, 'Canvas#lineTo() failed'
);
},
'test Canvas#arc()': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/arc.png';
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.moveTo(65,65);
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.stroke();
assertChecksum(
canvas
, path
, '3c48a221b24c582f46e39c16678b12dd'
, 'Canvas#arc() failed');
},
'test Canvas#bezierCurveTo()': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/bezierCurveTo.png';
ctx.beginPath();
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fill();
assertChecksum(
canvas
, path
, '5626a53780d77aecc490ec807ee0bc63'
, 'Canvas#bezierCurveTo() failed');
},
'test Canvas#fillStyle=': function(assert){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d')
, path = __dirname + '/fillStyle.png';
ctx.fillStyle = '#000';
ctx.fillRect(110, 110, 50, 50);
assert.equal('rgba(0,0,0,1)', ctx.fillStyle);
ctx.fillStyle = 'rgb(0,55,0)';
ctx.fillRect(10, 10, 50, 50);
assert.equal('rgba(0,55,0,1)', ctx.fillStyle);
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(60, 60, 50, 50);
assert.equal('rgba(0,0,0,0.5)', ctx.fillStyle);
assertChecksum(
canvas
, path
, '01632d060ba4702a53862a955382d30d'
, 'Canvas#fillStyle= failed');
}
}

19
wscript

@ -0,0 +1,19 @@
import glob
srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'
def set_options(opt):
opt.tool_options('compiler_cxx')
def configure(conf):
conf.check_tool('compiler_cxx')
conf.check_tool('node_addon')
conf.check_cfg(package='cairo', mandatory=1, args='--cflags --libs')
def build(bld):
obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
obj.target = 'canvas'
obj.source = bld.glob('src/*.cc')
obj.uselib = ['CAIRO']
Loading…
Cancel
Save