Browse Source

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
v1.x
TJ Holowaychuk 13 years ago
parent
commit
a063907832
  1. 3
      .gitignore
  2. 39
      examples/multi-page-pdf.js
  3. 22
      examples/small-pdf.js
  4. 79
      src/Canvas.cc
  5. 15
      src/Canvas.h
  6. 16
      src/CanvasRenderingContext2d.cc
  7. 1
      src/CanvasRenderingContext2d.h

3
.gitignore

@ -5,7 +5,8 @@ test/images/*.png
examples/*.png
examples/*.jpg
testing
test.png
out.png
out.pdf
.pomo
node_modules

39
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');

22
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');

79
src/Canvas.cc

@ -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,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<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;
}
}

15
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<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

16
src/CanvasRenderingContext2d.cc

@ -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);
@ -411,6 +412,21 @@ Context2d::New(const Arguments &args) {
return args.This();
}
/*
* 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.
*

1
src/CanvasRenderingContext2d.h

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

Loading…
Cancel
Save