Skip to content

Commit b809150

Browse files
committed
feat: ctx.addPageAsync()
This appears to be the correct usage of AsyncResource based on docs and other projects, but AsyncLocalStorage doesn't seem to work (or I'm not using it correctly). Doesn't really matter, we don't use it.
1 parent 4fcd6d3 commit b809150

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

src/CanvasRenderingContext2d.cc

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Context2d::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
100100
Nan::SetPrototypeMethod(ctor, "getImageData", GetImageData);
101101
Nan::SetPrototypeMethod(ctor, "createImageData", CreateImageData);
102102
Nan::SetPrototypeMethod(ctor, "addPage", AddPage);
103+
Nan::SetPrototypeMethod(ctor, "addPageAsync", AddPageAsync);
103104
Nan::SetPrototypeMethod(ctor, "save", Save);
104105
Nan::SetPrototypeMethod(ctor, "restore", Restore);
105106
Nan::SetPrototypeMethod(ctor, "rotate", Rotate);
@@ -746,7 +747,6 @@ NAN_GETTER(Context2d::GetFormat) {
746747
/*
747748
* Create a new page.
748749
*/
749-
750750
NAN_METHOD(Context2d::AddPage) {
751751
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
752752
if (context->canvas()->backend()->getName() != "pdf") {
@@ -761,6 +761,59 @@ NAN_METHOD(Context2d::AddPage) {
761761
return;
762762
}
763763

764+
struct AddPageInfo {
765+
Nan::Callback cb;
766+
Context2d* context;
767+
Nan::AsyncResource* async;
768+
int width;
769+
int height;
770+
};
771+
772+
void AddPageAsyncTask(uv_work_t* req) {
773+
AddPageInfo* pi = static_cast<AddPageInfo*>(req->data);
774+
cairo_show_page(pi->context->context());
775+
cairo_pdf_surface_set_size(pi->context->canvas()->surface(), pi->width, pi->height);
776+
}
777+
778+
void
779+
Context2d::AddPageAsyncTaskAfter(uv_work_t* req) {
780+
Nan::HandleScope scope;
781+
AddPageInfo* pi = static_cast<AddPageInfo*>(req->data);
782+
delete req;
783+
pi->cb.Call(0, {}, pi->async);
784+
pi->context->Unref();
785+
delete pi->async;
786+
delete pi;
787+
}
788+
789+
/*
790+
* Create a new page asynchronously. When using a PDF surface, Cairo actually
791+
* creates a recording surface, then when cairo_show_page() is called, it plays
792+
* back the operations *three times*. As such, this can be a very slow function
793+
* that benefits from running in a separate thread.
794+
*/
795+
NAN_METHOD(Context2d::AddPageAsync) {
796+
Context2d *context = Nan::ObjectWrap::Unwrap<Context2d>(info.This());
797+
if (context->canvas()->backend()->getName() != "pdf") {
798+
return Nan::ThrowError("only PDF canvases support .addPage()");
799+
}
800+
int width = Nan::To<int32_t>(info[1]).FromMaybe(0);
801+
int height = Nan::To<int32_t>(info[2]).FromMaybe(0);
802+
if (width < 1) width = context->canvas()->getWidth();
803+
if (height < 1) height = context->canvas()->getHeight();
804+
AddPageInfo* pi = new AddPageInfo;
805+
pi->cb.Reset(info[0].As<Function>());
806+
pi->context = context;
807+
pi->async = new Nan::AsyncResource("canvas:AddPageAsyncAfter");
808+
context->Ref();
809+
pi->width = width;
810+
pi->height = height;
811+
uv_loop_t* loop = uv_default_loop();
812+
uv_work_t* req = new uv_work_t;
813+
req->data = pi;
814+
uv_queue_work(loop, req, AddPageAsyncTask, (uv_after_work_cb)AddPageAsyncTaskAfter);
815+
}
816+
764817
/*
765818
* Put image data.
766819
*

src/CanvasRenderingContext2d.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class Context2d : public Nan::ObjectWrap {
105105
static NAN_METHOD(BeginPath);
106106
static NAN_METHOD(ClosePath);
107107
static NAN_METHOD(AddPage);
108+
static NAN_METHOD(AddPageAsync);
108109
static NAN_METHOD(Clip);
109110
static NAN_METHOD(Fill);
110111
static NAN_METHOD(Stroke);
@@ -203,6 +204,7 @@ class Context2d : public Nan::ObjectWrap {
203204
void restore();
204205
void setFontFromState();
205206
void resetState();
207+
static void AddPageAsyncTaskAfter(uv_work_t* req);
206208
inline PangoLayout *layout(){ return _layout; }
207209

208210
private:

test/canvas.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,19 @@ describe('Canvas', function () {
14471447
assert.strictEqual(gradient.toString(), '[object CanvasGradient]')
14481448
})
14491449

1450+
it('Context2d#addPageAsync()', function (done) {
1451+
const canvas = createCanvas(200, 200, 'pdf')
1452+
const ctx = canvas.getContext('2d')
1453+
ctx.strokeRect(10, 10, 50, 50)
1454+
ctx.addPageAsync(() => {
1455+
ctx.strokeRect(20, 20, 50, 50)
1456+
ctx.addPageAsync(() => {
1457+
// no crashes, at least
1458+
done();
1459+
});
1460+
})
1461+
});
1462+
14501463
describe('Context2d#putImageData()', function () {
14511464
it('throws for invalid arguments', function () {
14521465
const canvas = createCanvas(2, 1)

types/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ export class CanvasRenderingContext2D {
193193
* the canvas's initial size is used.
194194
*/
195195
addPage(width?: number, height?: number): void
196+
/**
197+
* Create a new page asynchronously. When using a PDF surface, Cairo
198+
* actually creates a recording surface, then when cairo_show_page() is
199+
* called, it plays back the operations *three times*. As such, this can be
200+
* a very slow function that benefits from running in a separate thread.
201+
*/
202+
addPageAsync(width?: number, height?: number): void
196203
save(): void;
197204
restore(): void;
198205
rotate(angle: number): void;

0 commit comments

Comments
 (0)