Permalink
Browse files

Added quick PDF support

need to clean things up, add async support, test images etc. I believe
some guys forked node-canvas to add some kind of image caching
for when an image appears several times within a PDF document, though
that may be out of scope
  • Loading branch information...
1 parent 9b6c6a8 commit a0639078327cf0c490d3da9a8ed908c3cbac42d9 @tj tj committed Apr 10, 2012
Showing with 154 additions and 21 deletions.
  1. +2 −1 .gitignore
  2. +39 −0 examples/multi-page-pdf.js
  3. +22 −0 examples/small-pdf.js
  4. +60 −19 src/Canvas.cc
  5. +14 −1 src/Canvas.h
  6. +16 −0 src/CanvasRenderingContext2d.cc
  7. +1 −0 src/CanvasRenderingContext2d.h
View
@@ -5,7 +5,8 @@ test/images/*.png
examples/*.png
examples/*.jpg
testing
-test.png
+out.png
+out.pdf
.pomo
node_modules
@@ -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');
View
@@ -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');
View
@@ -12,6 +12,7 @@
#include <string.h>
#include <node_buffer.h>
#include <node_version.h>
+#include <cairo-pdf.h>
#include "closure.h"
#ifdef HAVE_JPEG
@@ -53,9 +54,13 @@ Handle<Value>
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<Canvas>(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,19 +367,39 @@ 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);
+ }
}
/*
* Destroy cairo surface.
*/
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<Object> 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<Value> context = canvas->Get(String::New("context"));
- if (!context->IsUndefined()) {
- Context2d *context2d = ObjectWrap::Unwrap<Context2d>(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<Value> context = canvas->Get(String::New("context"));
+ if (!context->IsUndefined()) {
+ Context2d *context2d = ObjectWrap::Unwrap<Context2d>(context->ToObject());
+ cairo_t *prev = context2d->context();
+ context2d->setContext(cairo_create(surface()));
+ cairo_destroy(prev);
+ }
+ break;
}
}
View
@@ -27,13 +27,23 @@ using namespace node;
#endif
/*
+ * Canvas types.
+ */
+
+typedef enum {
+ CANVAS_TYPE_IMAGE,
+ CANVAS_TYPE_PDF
+} canvas_type_t;
+
+/*
* Canvas.
*/
class Canvas: public node::ObjectWrap {
public:
int width;
int height;
+ canvas_type_t type;
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static Handle<Value> 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<Object> canvas);
private:
~Canvas();
cairo_surface_t *_surface;
+ void *_closure;
};
#endif
@@ -62,6 +62,7 @@ Context2d::Initialize(Handle<Object> target) {
Local<ObjectTemplate> 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);
@@ -412,6 +413,21 @@ Context2d::New(const Arguments &args) {
}
/*
+ * Create a new page.
+ */
+
+Handle<Value>
+Context2d::NextPage(const Arguments &args) {
+ HandleScope scope;
+ Context2d *context = ObjectWrap::Unwrap<Context2d>(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.
*
* - imageData, dx, dy
@@ -57,6 +57,7 @@ class Context2d: public node::ObjectWrap {
static Handle<Value> IsPointInPath(const Arguments &args);
static Handle<Value> BeginPath(const Arguments &args);
static Handle<Value> ClosePath(const Arguments &args);
+ static Handle<Value> NextPage(const Arguments &args);
static Handle<Value> Clip(const Arguments &args);
static Handle<Value> Fill(const Arguments &args);
static Handle<Value> Stroke(const Arguments &args);

0 comments on commit a063907

Please sign in to comment.