Skip to content

Commit

Permalink
Crude PDF stream implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kkoopa committed Jun 20, 2016
1 parent b470ce8 commit 003e803
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 0 deletions.
26 changes: 26 additions & 0 deletions lib/canvas.js
Expand Up @@ -16,6 +16,7 @@ var canvas = require('./bindings')
, cairoVersion = canvas.cairoVersion
, Context2d = require('./context2d')
, PNGStream = require('./pngstream')
, PDFStream = require('./pdfstream')
, JPEGStream = require('./jpegstream')
, FontFace = canvas.FontFace
, fs = require('fs')
Expand Down Expand Up @@ -70,6 +71,7 @@ if (canvas.freetypeVersion) {

exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
exports.PDFStream = PDFStream;
exports.JPEGStream = JPEGStream;
exports.Image = Image;
exports.ImageData = canvas.ImageData;
Expand Down Expand Up @@ -161,6 +163,30 @@ Canvas.prototype.createSyncPNGStream = function(){
return new PNGStream(this, true);
};

/**
* Create a `PDFStream` for `this` canvas.
*
* @return {PDFStream}
* @api public
*/

Canvas.prototype.pdfStream =
Canvas.prototype.createPDFStream = function(){
return new PDFStream(this);
};

/**
* Create a synchronous `PDFStream` for `this` canvas.
*
* @return {PDFStream}
* @api public
*/

Canvas.prototype.syncPDFStream =
Canvas.prototype.createSyncPDFStream = function(){
return new PDFStream(this, true);
};

/**
* Create a `JPEGStream` for `this` canvas.
*
Expand Down
59 changes: 59 additions & 0 deletions lib/pdfstream.js
@@ -0,0 +1,59 @@
'use strict';

/*!
* Canvas - PDFStream
*/

/**
* Module dependencies.
*/

var Stream = require('stream').Stream;

/**
* Initialize a `PDFStream` with the given `canvas`.
*
* "data" events are emitted with `Buffer` chunks, once complete the
* "end" event is emitted. The following example will stream to a file
* named "./my.pdf".
*
* var out = fs.createWriteStream(__dirname + '/my.pdf')
* , stream = canvas.createPDFStream();
*
* stream.pipe(out);
*
* @param {Canvas} canvas
* @param {Boolean} sync
* @api public
*/

var PDFStream = module.exports = function PDFStream(canvas, sync) {
var self = this
, method = sync
? 'streamPDFSync'
: 'streamPDF';
this.sync = sync;
this.canvas = canvas;
this.readable = true;
// TODO: implement async
if ('streamPDF' == method) method = 'streamPDFSync';
process.nextTick(function(){
canvas[method](function(err, chunk, len){
if (err) {
self.emit('error', err);
self.readable = false;
} else if (len) {
self.emit('data', chunk, len);
} else {
self.emit('end');
self.readable = false;
}
});
});
};

/**
* Inherit from `EventEmitter`.
*/

PDFStream.prototype.__proto__ = Stream.prototype;
79 changes: 79 additions & 0 deletions src/Canvas.cc
Expand Up @@ -40,6 +40,7 @@ Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
#ifdef HAVE_JPEG
Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
#endif
Expand Down Expand Up @@ -434,6 +435,84 @@ NAN_METHOD(Canvas::StreamPNGSync) {
return;
}

/*
* Canvas::StreamPDF FreeCallback
*/

void stream_pdf_free(char *, void *) {}

/*
* Canvas::StreamPDF callback.
*/

static cairo_status_t
streamPDF(void *c, const uint8_t *data, unsigned len) {
Nan::HandleScope scope;
closure_t *closure = static_cast<closure_t *>(c);
Local<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
Local<Value> argv[3] = {
Nan::Null()
, buf
, Nan::New<Number>(len) };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), closure->fn, 3, argv);
return CAIRO_STATUS_SUCCESS;
}


cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, void *closure) {
closure_t *pdf_closure = static_cast<closure_t *>(closure);
size_t whole_chunks = pdf_closure->len / PAGE_SIZE;
size_t remainder = pdf_closure->len - whole_chunks * PAGE_SIZE;

for (size_t i = 0; i < whole_chunks; ++i) {
write_func(pdf_closure, &pdf_closure->data[i * PAGE_SIZE], PAGE_SIZE);
}

if (remainder) {
write_func(pdf_closure, &pdf_closure->data[whole_chunks * PAGE_SIZE], remainder);
}

return CAIRO_STATUS_SUCCESS;
}

/*
* Stream PDF data synchronously.
*/

NAN_METHOD(Canvas::StreamPDFSync) {
if (!info[0]->IsFunction())
return Nan::ThrowTypeError("callback function required");

Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());

if (!canvas->isPDF())
return Nan::ThrowTypeError("wrong canvas type");

cairo_surface_finish(canvas->surface());

closure_t closure;
closure.data = static_cast<closure_t *>(canvas->closure())->data;
closure.len = static_cast<closure_t *>(canvas->closure())->len;
closure.fn = info[0].As<Function>();

Nan::TryCatch try_catch;

cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &closure);

if (try_catch.HasCaught()) {
try_catch.ReThrow();
} else if (status) {
Local<Value> error = Canvas::Error(status);
Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 1, &error);
} else {
Local<Value> argv[3] = {
Nan::Null()
, Nan::Null()
, Nan::New<Uint32>(0) };
Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 3, argv);
}
}

/*
* Stream JPEG data synchronously.
*/
Expand Down
1 change: 1 addition & 0 deletions src/Canvas.h
Expand Up @@ -62,6 +62,7 @@ class Canvas: public Nan::ObjectWrap {
static NAN_SETTER(SetWidth);
static NAN_SETTER(SetHeight);
static NAN_METHOD(StreamPNGSync);
static NAN_METHOD(StreamPDFSync);
static NAN_METHOD(StreamJPEGSync);
static Local<Value> Error(cairo_status_t status);
#if NODE_VERSION_AT_LEAST(0, 6, 0)
Expand Down
18 changes: 18 additions & 0 deletions test/canvas.test.js
Expand Up @@ -802,6 +802,24 @@ describe('Canvas', function () {
});
});

it('Canvas#createSyncPDFStream()', function (done) {
var canvas = new Canvas(20, 20, 'pdf');
var stream = canvas.createSyncPDFStream();
var firstChunk = true;
stream.on('data', function (chunk) {
if (firstChunk) {
firstChunk = false;
assert.equal('PDF', chunk.slice(1, 4).toString());
}
});
stream.on('end', function () {
done();
});
stream.on('error', function (err) {
done(err);
});
});

it('Canvas#jpegStream()', function (done) {
var canvas = new Canvas(640, 480);
var stream = canvas.jpegStream();
Expand Down

0 comments on commit 003e803

Please sign in to comment.