diff --git a/.gitignore b/.gitignore index 9c615a4..6e9fcd3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ test/images/*.png examples/*.png examples/*.jpg testing -test.png +out.png +out.pdf .pomo node_modules diff --git a/examples/multi-page-pdf.js b/examples/multi-page-pdf.js new file mode 100644 index 0000000..1bc7371 --- /dev/null +++ b/examples/multi-page-pdf.js @@ -0,0 +1,39 @@ + +var Canvas = require('../') + , canvas = new Canvas(500, 500, 'pdf') + , ctx = canvas.getContext('2d') + , fs = require('fs'); + +var x, y; + +function reset() { + x = 50; + y = 80; +} + +function h1(str) { + ctx.font = '22px Helvetica'; + ctx.fillText(str, x, y); +} + +function p(str) { + ctx.font = '10px Arial'; + ctx.fillText(str, x, y += 20); +} + +reset(); +h1('PDF demo'); +p('Multi-page PDF demonstration'); +ctx.nextPage(); + +reset(); +h1('Page #2'); +p('This is the second page'); +ctx.nextPage(); + +reset(); +h1('Page #3'); +p('This is the third page'); + +fs.writeFile('out.pdf', canvas.toBuffer()); +console.log('created out.pdf'); \ No newline at end of file diff --git a/examples/small-pdf.js b/examples/small-pdf.js new file mode 100644 index 0000000..c400f1f --- /dev/null +++ b/examples/small-pdf.js @@ -0,0 +1,22 @@ + +var Canvas = require('../') + , canvas = new Canvas(500, 500, 'pdf') + , ctx = canvas.getContext('2d') + , fs = require('fs'); + +var y = 80 + , x = 50; + +ctx.font = '22px Helvetica'; +ctx.fillText('node-canvas pdf', x, y); + +ctx.font = '10px Arial'; +ctx.fillText('Just a quick example of PDFs with node-canvas', x, y += 20); + +ctx.globalAlpha = .5; +ctx.fillRect(x, y += 20, 10, 10); +ctx.fillRect(x += 20, y, 10, 10); +ctx.fillRect(x += 20, y, 10, 10); + +fs.writeFile('out.pdf', canvas.toBuffer()); +console.log('created out.pdf'); \ No newline at end of file diff --git a/src/Canvas.cc b/src/Canvas.cc index f770d8f..c4a186b 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include "closure.h" #ifdef HAVE_JPEG @@ -53,9 +54,13 @@ Handle Canvas::New(const Arguments &args) { HandleScope scope; int width = 0, height = 0; + canvas_type_t type = CANVAS_TYPE_IMAGE; if (args[0]->IsNumber()) width = args[0]->Uint32Value(); if (args[1]->IsNumber()) height = args[1]->Uint32Value(); - Canvas *canvas = new Canvas(width, height); + if (args[2]->IsString()) type = !strcmp("pdf", *String::AsciiValue(args[2])) + ? CANVAS_TYPE_PDF + : CANVAS_TYPE_IMAGE; + Canvas *canvas = new Canvas(width, height, type); canvas->Wrap(args.This()); return args.This(); } @@ -213,6 +218,15 @@ Canvas::ToBuffer(const Arguments &args) { cairo_status_t status; Canvas *canvas = ObjectWrap::Unwrap(args.This()); + // TODO: async / move this out + if (canvas->isPDF()) { + cairo_surface_finish(canvas->surface()); + closure_t *closure = (closure_t *) canvas->closure(); + Buffer *buf = Buffer::New(closure->len); + memcpy(Buffer::Data(buf), closure->data, closure->len); + return buf->handle_; + } + // Async if (args[0]->IsFunction()) { closure_t *closure = (closure_t *) malloc(sizeof(closure_t)); @@ -353,11 +367,24 @@ Canvas::StreamJPEGSync(const Arguments &args) { * Initialize cairo surface. */ -Canvas::Canvas(int w, int h): ObjectWrap() { +Canvas::Canvas(int w, int h, canvas_type_t t): ObjectWrap() { + type = t; width = w; height = h; - _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); - V8::AdjustAmountOfExternalAllocatedMemory(4 * width * height); + _surface = NULL; + _closure = NULL; + + if (CANVAS_TYPE_PDF == t) { + _closure = malloc(sizeof(closure_t)); + assert(_closure); + cairo_status_t status = closure_init((closure_t *) _closure, this); + assert(status == CAIRO_STATUS_SUCCESS); + _surface = cairo_pdf_surface_create_for_stream(toBuffer, _closure, w, h); + } else { + _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); + assert(_surface); + V8::AdjustAmountOfExternalAllocatedMemory(4 * w * h); + } } /* @@ -365,7 +392,14 @@ Canvas::Canvas(int w, int h): ObjectWrap() { */ Canvas::~Canvas() { - cairo_surface_destroy(_surface); + switch (type) { + case CANVAS_TYPE_PDF: + closure_destroy((closure_t *) _closure); + break; + case CANVAS_TYPE_IMAGE: + cairo_surface_destroy(_surface); + break; + } } /* @@ -374,20 +408,27 @@ Canvas::~Canvas() { void Canvas::resurface(Handle canvas) { - // Re-surface - int old_width = cairo_image_surface_get_width(_surface); - int old_height = cairo_image_surface_get_height(_surface); - cairo_surface_destroy(_surface); - _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - V8::AdjustAmountOfExternalAllocatedMemory(4 * (width * height - old_width * old_height)); - - // Reset context - Handle context = canvas->Get(String::New("context")); - if (!context->IsUndefined()) { - Context2d *context2d = ObjectWrap::Unwrap(context->ToObject()); - cairo_t *prev = context2d->context(); - context2d->setContext(cairo_create(surface())); - cairo_destroy(prev); + switch (type) { + case CANVAS_TYPE_PDF: + cairo_pdf_surface_set_size(_surface, width, height); + break; + case CANVAS_TYPE_IMAGE: + // Re-surface + int old_width = cairo_image_surface_get_width(_surface); + int old_height = cairo_image_surface_get_height(_surface); + cairo_surface_destroy(_surface); + _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + V8::AdjustAmountOfExternalAllocatedMemory(4 * (width * height - old_width * old_height)); + + // Reset context + Handle context = canvas->Get(String::New("context")); + if (!context->IsUndefined()) { + Context2d *context2d = ObjectWrap::Unwrap(context->ToObject()); + cairo_t *prev = context2d->context(); + context2d->setContext(cairo_create(surface())); + cairo_destroy(prev); + } + break; } } diff --git a/src/Canvas.h b/src/Canvas.h index c2def8d..846693f 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -26,6 +26,15 @@ using namespace node; #define CANVAS_MAX_STATES 64 #endif +/* + * Canvas types. + */ + +typedef enum { + CANVAS_TYPE_IMAGE, + CANVAS_TYPE_PDF +} canvas_type_t; + /* * Canvas. */ @@ -34,6 +43,7 @@ class Canvas: public node::ObjectWrap { public: int width; int height; + canvas_type_t type; static Persistent constructor; static void Initialize(Handle target); static Handle New(const Arguments &args); @@ -59,15 +69,18 @@ class Canvas: public node::ObjectWrap { static int EIO_AfterToBuffer(eio_req *req); #endif + inline bool isPDF(){ return CANVAS_TYPE_PDF == type; } inline cairo_surface_t *surface(){ return _surface; } + inline void *closure(){ return _closure; } inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); } inline int stride(){ return cairo_image_surface_get_stride(_surface); } - Canvas(int width, int height); + Canvas(int width, int height, canvas_type_t type); void resurface(Handle canvas); private: ~Canvas(); cairo_surface_t *_surface; + void *_closure; }; #endif diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 4c0c885..553e34b 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -62,6 +62,7 @@ Context2d::Initialize(Handle target) { Local proto = constructor->PrototypeTemplate(); NODE_SET_PROTOTYPE_METHOD(constructor, "drawImage", DrawImage); NODE_SET_PROTOTYPE_METHOD(constructor, "putImageData", PutImageData); + NODE_SET_PROTOTYPE_METHOD(constructor, "nextPage", NextPage); NODE_SET_PROTOTYPE_METHOD(constructor, "save", Save); NODE_SET_PROTOTYPE_METHOD(constructor, "restore", Restore); NODE_SET_PROTOTYPE_METHOD(constructor, "rotate", Rotate); @@ -411,6 +412,21 @@ Context2d::New(const Arguments &args) { return args.This(); } +/* + * Create a new page. + */ + +Handle +Context2d::NextPage(const Arguments &args) { + HandleScope scope; + Context2d *context = ObjectWrap::Unwrap(args.This()); + if (!context->canvas()->isPDF()) { + return ThrowException(Exception::Error(String::New("only PDF canvases support .nextPage()"))); + } + cairo_show_page(context->context()); + return Undefined(); +} + /* * Put image data. * diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index ad08331..b91784f 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -57,6 +57,7 @@ class Context2d: public node::ObjectWrap { static Handle IsPointInPath(const Arguments &args); static Handle BeginPath(const Arguments &args); static Handle ClosePath(const Arguments &args); + static Handle NextPage(const Arguments &args); static Handle Clip(const Arguments &args); static Handle Fill(const Arguments &args); static Handle Stroke(const Arguments &args);