Browse Source

Merge branch 'async'

v1.x
Tj Holowaychuk 14 years ago
parent
commit
af9425395f
  1. 32
      lib/buffer.js
  2. 54
      lib/canvas.js
  3. 111
      src/Canvas.cc
  4. 1
      src/Canvas.h
  5. 28
      test/canvas.test.js

32
lib/buffer.js

@ -1,32 +0,0 @@
/*!
* Canvas - Buffer
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
* MIT Licensed
*/
/**
* Concatenate `this` Buffer with `other`.
*
* @param {Buffer} other
* @return {Buffer}
* @api public
*/
Buffer.prototype.concat = function(other) {
var len = this.length
, buf = new Buffer(len + other.length);
this.copy(buf, 0, 0);
other.copy(buf, len, 0);
return buf;
};
// patch if not v0.2 of node.js and Buffer.isBuffer fails for SlowBuffer
if (process.version.indexOf("v0.2") == -1) {
var SlowBuffer = process.binding('buffer').SlowBuffer;
if (!Buffer.isBuffer(new SlowBuffer(10))) {
Buffer.isBuffer = function (b) {
return (b instanceof Buffer) || (b instanceof SlowBuffer);
};
}
}

54
lib/canvas.js

@ -41,12 +41,6 @@ exports.cairoVersion = cairoVersion;
exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
/**
* Buffer extensions.
*/
require('./buffer');
/**
* Context2d implementation.
*/
@ -104,37 +98,33 @@ Canvas.prototype.createSyncPNGStream = function(){
};
/**
* Return a `Buffer` instance consisting of the PNG image data.
*
* @return {Buffer}
* @api public
*/
Canvas.prototype.toBuffer = function(){
var buf;
this.streamPNGSync(function(err, chunk, len){
if (err) throw err;
if (len) {
buf = buf
? buf.concat(chunk)
: chunk;
}
});
return buf;
};
/**
* Return a data url.
* Return a data url. Pass a function for async support.
*
* @param {String} type
* @param {String|Function} type
* @param {Function} fn
* @return {String}
* @api public
*/
Canvas.prototype.toDataURL = function(type){
// TODO: jpeg / svg / pdf :)
Canvas.prototype.toDataURL = function(type, fn){
// Default to png
type = type || 'image/png';
// Allow callback as first arg
if ('function' == typeof type) fn = type, type = 'image/png';
// Throw on non-png
if ('image/png' != type) throw new Error('currently only image/png is supported');
return 'data:' + type
+ ';base64,' + this.toBuffer().toString('base64');
var prefix = 'data:' + type + ';base64,';
if (fn) {
this.toBuffer(function(err, buf){
if (err) return fn(err);
var str = 'data:' + type
fn(null, prefix + buf.toString('base64'));
});
} else {
return prefix + this.toBuffer().toString('base64');
}
};

111
src/Canvas.cc

@ -18,7 +18,12 @@ using namespace node;
*/
typedef struct {
Persistent<Function> pfn;
Handle<Function> fn;
unsigned len;
uint8_t *data;
Canvas *canvas;
cairo_status_t status;
} closure_t;
/*
@ -33,6 +38,7 @@ Canvas::Initialize(Handle<Object> target) {
t->SetClassName(String::NewSymbol("Canvas"));
Local<ObjectTemplate> proto = t->PrototypeTemplate();
NODE_SET_PROTOTYPE_METHOD(t, "toBuffer", ToBuffer);
NODE_SET_PROTOTYPE_METHOD(t, "streamPNGSync", StreamPNGSync);
proto->SetAccessor(String::NewSymbol("width"), GetWidth, SetWidth);
proto->SetAccessor(String::NewSymbol("height"), GetHeight, SetHeight);
@ -102,12 +108,113 @@ Canvas::SetHeight(Local<String> prop, Local<Value> val, const AccessorInfo &info
}
}
/*
* Canvas::ToBuffer callback.
*/
static cairo_status_t
toBuffer(void *c, const uint8_t *data, unsigned len) {
closure_t *closure = (closure_t *) c;
// TODO: mem handling
if (closure->len) {
closure->data = (uint8_t *) realloc(closure->data, closure->len + len);
memcpy(closure->data + closure->len, data, len);
closure->len += len;
} else {
closure->data = (uint8_t *) malloc(len);
memcpy(closure->data, data, len);
closure->len += len;
}
return CAIRO_STATUS_SUCCESS;
}
/*
* EIO toBuffer callback.
*/
static int
EIO_ToBuffer(eio_req *req) {
closure_t *closure = (closure_t *) req->data;
closure->status = cairo_surface_write_to_png_stream(
closure->canvas->getSurface()
, toBuffer
, closure);
return 0;
}
/*
* EIO after toBuffer callback.
*/
static int
EIO_AfterToBuffer(eio_req *req) {
HandleScope scope;
closure_t *closure = (closure_t *) req->data;
ev_unref(EV_DEFAULT_UC);
if (closure->status) {
Handle<Value> argv[1] = { Canvas::Error(closure->status) };
closure->pfn->Call(Context::GetCurrent()->Global(), 1, argv);
} else {
Buffer *buf = Buffer::New(closure->len);
memcpy(buf->data(), closure->data, closure->len);
Handle<Value> argv[2] = { Null(), buf->handle_ };
closure->pfn->Call(Context::GetCurrent()->Global(), 2, argv);
}
closure->pfn.Dispose();
delete closure;
return 0;
}
/*
* Convert PNG data to a node::Buffer, async when a
* callback function is passed.
*/
Handle<Value>
Canvas::ToBuffer(const Arguments &args) {
HandleScope scope;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
// Async
if (args[0]->IsFunction()) {
closure_t *closure = new closure_t;
closure->len = 0;
closure->canvas = canvas;
// TODO: only one callback fn in closure
canvas->Ref();
closure->pfn = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure);
ev_ref(EV_DEFAULT_UC);
return Undefined();
} else {
closure_t closure;
closure.len = 0;
TryCatch try_catch;
cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), toBuffer, &closure);
if (try_catch.HasCaught()) {
return try_catch.ReThrow();
} else if (status) {
return ThrowException(Canvas::Error(status));
} else {
Buffer *buf = Buffer::New(closure.len);
memcpy(buf->data(), closure.data, closure.len);
return buf->handle_;
}
}
}
/*
* Canvas::StreamPNG callback.
*/
static cairo_status_t
writeToBuffer(void *c, const uint8_t *data, unsigned len) {
streamPNG(void *c, const uint8_t *data, unsigned len) {
closure_t *closure = (closure_t *) c;
Buffer *buf = Buffer::New(len);
#if NODE_VERSION_AT_LEAST(0,3,0)
@ -136,7 +243,7 @@ Canvas::StreamPNGSync(const Arguments &args) {
closure.fn = Handle<Function>::Cast(args[0]);
TryCatch try_catch;
cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure);
cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), streamPNG, &closure);
if (try_catch.HasCaught()) {
return try_catch.ReThrow();

1
src/Canvas.h

@ -52,6 +52,7 @@ class Canvas: public node::ObjectWrap {
int height;
static void Initialize(Handle<Object> target);
static Handle<Value> New(const Arguments &args);
static Handle<Value> ToBuffer(const Arguments &args);
static Handle<Value> GetWidth(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetHeight(Local<String> prop, const AccessorInfo &info);
static void SetWidth(Local<String> prop, Local<Value> val, const AccessorInfo &info);

28
test/canvas.test.js

@ -237,7 +237,17 @@ module.exports = {
},
'test Canvas#toBuffer()': function(assert){
assert.ok(Buffer.isBuffer(new Canvas(200, 200).toBuffer()), 'Canvas#toBuffer() failed');
var buf = new Canvas(200,200).toBuffer();
assert.equal('PNG', buf.slice(1,4).toString());
assert.length(buf, 252);
},
'test Canvas#toBuffer() async': function(assert){
new Canvas(200, 200).toBuffer(function(err, buf){
assert.ok(!err);
assert.equal('PNG', buf.slice(1,4).toString());
assert.length(buf, 252);
});
},
'test Canvas#toDataURL()': function(assert){
@ -261,7 +271,7 @@ module.exports = {
assert.equal(str, canvas.toDataURL(), 'Canvas#toDataURL() failed');
assert.equal(str, canvas.toDataURL('image/png'), 'Canvas#toDataURL() failed');
var err;
try {
canvas.toDataURL('image/jpeg');
@ -269,5 +279,19 @@ module.exports = {
err = e;
}
assert.equal('currently only image/png is supported', err.message);
},
'test Canvas#toDataURL() async': function(assert){
new Canvas(200,200).toDataURL(function(err, str){
assert.ok(!err);
assert.length(str, 358);
});
},
'test Canvas#toDataURL() async with type': function(assert){
new Canvas(200,200).toDataURL('image/png', function(err, str){
assert.ok(!err);
assert.length(str, 358);
});
}
}
Loading…
Cancel
Save