Browse Source

Crude PDF stream implementation

v1.x
Benjamin Byholm 9 years ago
parent
commit
003e803c3a
  1. 26
      lib/canvas.js
  2. 59
      lib/pdfstream.js
  3. 79
      src/Canvas.cc
  4. 1
      src/Canvas.h
  5. 18
      test/canvas.test.js

26
lib/canvas.js

@ -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')
@ -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;
@ -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.
*

59
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
src/Canvas.cc

@ -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
@ -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.
*/

1
src/Canvas.h

@ -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)

18
test/canvas.test.js

@ -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();

Loading…
Cancel
Save