Turn audio into a shareable video. forked from nypublicradio/audiogram

Canvas.cc 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. //
  2. // Canvas.cc
  3. //
  4. // Copyright (c) 2010 LearnBoost <tj@learnboost.com>
  5. //
  6. #include <assert.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <node_buffer.h>
  10. #include <node_version.h>
  11. #include <glib.h>
  12. #include <cairo-pdf.h>
  13. #include <cairo-svg.h>
  14. #include "Canvas.h"
  15. #include "PNG.h"
  16. #include "CanvasRenderingContext2d.h"
  17. #include "closure.h"
  18. #include "register_font.h"
  19. #ifdef HAVE_JPEG
  20. #include "JPEGStream.h"
  21. #endif
  22. #define GENERIC_FACE_ERROR \
  23. "The second argument to registerFont is required, and should be an object " \
  24. "with at least a family (string) and optionally weight (string/number) " \
  25. "and style (string)."
  26. Nan::Persistent<FunctionTemplate> Canvas::constructor;
  27. /*
  28. * Initialize Canvas.
  29. */
  30. void
  31. Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
  32. Nan::HandleScope scope;
  33. // Constructor
  34. Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
  35. constructor.Reset(ctor);
  36. ctor->InstanceTemplate()->SetInternalFieldCount(1);
  37. ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
  38. // Prototype
  39. Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
  40. Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
  41. Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
  42. Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
  43. #ifdef HAVE_JPEG
  44. Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
  45. #endif
  46. Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
  47. Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride);
  48. Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
  49. Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
  50. Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New<Uint32>(PNG_NO_FILTERS));
  51. Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
  52. Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
  53. Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
  54. Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
  55. Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
  56. Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
  57. // Class methods
  58. Nan::SetMethod(ctor, "registerFont", RegisterFont);
  59. Nan::Set(target, Nan::New("Canvas").ToLocalChecked(), ctor->GetFunction());
  60. }
  61. /*
  62. * Initialize a Canvas with the given width and height.
  63. */
  64. NAN_METHOD(Canvas::New) {
  65. if (!info.IsConstructCall()) {
  66. return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
  67. }
  68. int width = 0, height = 0;
  69. canvas_type_t type = CANVAS_TYPE_IMAGE;
  70. if (info[0]->IsNumber()) width = info[0]->Uint32Value();
  71. if (info[1]->IsNumber()) height = info[1]->Uint32Value();
  72. if (info[2]->IsString()) type = !strcmp("pdf", *String::Utf8Value(info[2]))
  73. ? CANVAS_TYPE_PDF
  74. : !strcmp("svg", *String::Utf8Value(info[2]))
  75. ? CANVAS_TYPE_SVG
  76. : CANVAS_TYPE_IMAGE;
  77. Canvas *canvas = new Canvas(width, height, type);
  78. canvas->Wrap(info.This());
  79. info.GetReturnValue().Set(info.This());
  80. }
  81. /*
  82. * Get type string.
  83. */
  84. NAN_GETTER(Canvas::GetType) {
  85. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  86. info.GetReturnValue().Set(Nan::New<String>(canvas->isPDF() ? "pdf" : canvas->isSVG() ? "svg" : "image").ToLocalChecked());
  87. }
  88. /*
  89. * Get stride.
  90. */
  91. NAN_GETTER(Canvas::GetStride) {
  92. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  93. info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
  94. }
  95. /*
  96. * Get width.
  97. */
  98. NAN_GETTER(Canvas::GetWidth) {
  99. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  100. info.GetReturnValue().Set(Nan::New<Number>(canvas->width));
  101. }
  102. /*
  103. * Set width.
  104. */
  105. NAN_SETTER(Canvas::SetWidth) {
  106. if (value->IsNumber()) {
  107. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  108. canvas->width = value->Uint32Value();
  109. canvas->resurface(info.This());
  110. }
  111. }
  112. /*
  113. * Get height.
  114. */
  115. NAN_GETTER(Canvas::GetHeight) {
  116. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  117. info.GetReturnValue().Set(Nan::New<Number>(canvas->height));
  118. }
  119. /*
  120. * Set height.
  121. */
  122. NAN_SETTER(Canvas::SetHeight) {
  123. if (value->IsNumber()) {
  124. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  125. canvas->height = value->Uint32Value();
  126. canvas->resurface(info.This());
  127. }
  128. }
  129. /*
  130. * Canvas::ToBuffer callback.
  131. */
  132. static cairo_status_t
  133. toBuffer(void *c, const uint8_t *data, unsigned len) {
  134. closure_t *closure = (closure_t *) c;
  135. if (closure->len + len > closure->max_len) {
  136. uint8_t *data;
  137. unsigned max = closure->max_len;
  138. do {
  139. max *= 2;
  140. } while (closure->len + len > max);
  141. data = (uint8_t *) realloc(closure->data, max);
  142. if (!data) return CAIRO_STATUS_NO_MEMORY;
  143. closure->data = data;
  144. closure->max_len = max;
  145. }
  146. memcpy(closure->data + closure->len, data, len);
  147. closure->len += len;
  148. return CAIRO_STATUS_SUCCESS;
  149. }
  150. /*
  151. * EIO toBuffer callback.
  152. */
  153. #if NODE_VERSION_AT_LEAST(0, 6, 0)
  154. void
  155. Canvas::ToBufferAsync(uv_work_t *req) {
  156. #elif NODE_VERSION_AT_LEAST(0, 5, 4)
  157. void
  158. Canvas::EIO_ToBuffer(eio_req *req) {
  159. #else
  160. int
  161. Canvas::EIO_ToBuffer(eio_req *req) {
  162. #endif
  163. closure_t *closure = (closure_t *) req->data;
  164. closure->status = canvas_write_to_png_stream(
  165. closure->canvas->surface()
  166. , toBuffer
  167. , closure);
  168. #if !NODE_VERSION_AT_LEAST(0, 5, 4)
  169. return 0;
  170. #endif
  171. }
  172. /*
  173. * EIO after toBuffer callback.
  174. */
  175. #if NODE_VERSION_AT_LEAST(0, 6, 0)
  176. void
  177. Canvas::ToBufferAsyncAfter(uv_work_t *req) {
  178. #else
  179. int
  180. Canvas::EIO_AfterToBuffer(eio_req *req) {
  181. #endif
  182. Nan::HandleScope scope;
  183. closure_t *closure = (closure_t *) req->data;
  184. #if NODE_VERSION_AT_LEAST(0, 6, 0)
  185. delete req;
  186. #else
  187. ev_unref(EV_DEFAULT_UC);
  188. #endif
  189. if (closure->status) {
  190. Local<Value> argv[1] = { Canvas::Error(closure->status) };
  191. closure->pfn->Call(1, argv);
  192. } else {
  193. Local<Object> buf = Nan::CopyBuffer((char*)closure->data, closure->len).ToLocalChecked();
  194. memcpy(Buffer::Data(buf), closure->data, closure->len);
  195. Local<Value> argv[2] = { Nan::Null(), buf };
  196. closure->pfn->Call(2, argv);
  197. }
  198. closure->canvas->Unref();
  199. delete closure->pfn;
  200. closure_destroy(closure);
  201. free(closure);
  202. #if !NODE_VERSION_AT_LEAST(0, 6, 0)
  203. return 0;
  204. #endif
  205. }
  206. /*
  207. * Convert PNG data to a node::Buffer, async when a
  208. * callback function is passed.
  209. */
  210. NAN_METHOD(Canvas::ToBuffer) {
  211. cairo_status_t status;
  212. uint32_t compression_level = 6;
  213. uint32_t filter = PNG_ALL_FILTERS;
  214. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  215. // TODO: async / move this out
  216. if (canvas->isPDF() || canvas->isSVG()) {
  217. cairo_surface_finish(canvas->surface());
  218. closure_t *closure = (closure_t *) canvas->closure();
  219. Local<Object> buf = Nan::CopyBuffer((char*) closure->data, closure->len).ToLocalChecked();
  220. info.GetReturnValue().Set(buf);
  221. return;
  222. }
  223. if (info.Length() >= 1 && info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
  224. // Return raw ARGB data -- just a memcpy()
  225. cairo_surface_t *surface = canvas->surface();
  226. cairo_surface_flush(surface);
  227. const unsigned char *data = cairo_image_surface_get_data(surface);
  228. Local<Object> buf = Nan::CopyBuffer(reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
  229. info.GetReturnValue().Set(buf);
  230. return;
  231. }
  232. if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
  233. if (!info[1]->IsUndefined()) {
  234. bool good = true;
  235. if (info[1]->IsNumber()) {
  236. compression_level = info[1]->Uint32Value();
  237. } else if (info[1]->IsString()) {
  238. if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
  239. compression_level = 0;
  240. } else {
  241. uint32_t tmp = info[1]->Uint32Value();
  242. if (tmp == 0) {
  243. good = false;
  244. } else {
  245. compression_level = tmp;
  246. }
  247. }
  248. } else {
  249. good = false;
  250. }
  251. if (good) {
  252. if (compression_level > 9) {
  253. return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
  254. }
  255. } else {
  256. return Nan::ThrowTypeError("Compression level must be a number.");
  257. }
  258. }
  259. if (!info[2]->IsUndefined()) {
  260. if (info[2]->IsUint32()) {
  261. filter = info[2]->Uint32Value();
  262. } else {
  263. return Nan::ThrowTypeError("Invalid filter value.");
  264. }
  265. }
  266. }
  267. // Async
  268. if (info[0]->IsFunction()) {
  269. closure_t *closure = (closure_t *) malloc(sizeof(closure_t));
  270. status = closure_init(closure, canvas, compression_level, filter);
  271. // ensure closure is ok
  272. if (status) {
  273. closure_destroy(closure);
  274. free(closure);
  275. return Nan::ThrowError(Canvas::Error(status));
  276. }
  277. // TODO: only one callback fn in closure
  278. canvas->Ref();
  279. closure->pfn = new Nan::Callback(info[0].As<Function>());
  280. #if NODE_VERSION_AT_LEAST(0, 6, 0)
  281. uv_work_t* req = new uv_work_t;
  282. req->data = closure;
  283. uv_queue_work(uv_default_loop(), req, ToBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
  284. #else
  285. eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure);
  286. ev_ref(EV_DEFAULT_UC);
  287. #endif
  288. return;
  289. // Sync
  290. } else {
  291. closure_t closure;
  292. status = closure_init(&closure, canvas, compression_level, filter);
  293. // ensure closure is ok
  294. if (status) {
  295. closure_destroy(&closure);
  296. return Nan::ThrowError(Canvas::Error(status));
  297. }
  298. TryCatch try_catch;
  299. status = canvas_write_to_png_stream(canvas->surface(), toBuffer, &closure);
  300. if (try_catch.HasCaught()) {
  301. closure_destroy(&closure);
  302. try_catch.ReThrow();
  303. return;
  304. } else if (status) {
  305. closure_destroy(&closure);
  306. return Nan::ThrowError(Canvas::Error(status));
  307. } else {
  308. Local<Object> buf = Nan::CopyBuffer((char *)closure.data, closure.len).ToLocalChecked();
  309. closure_destroy(&closure);
  310. info.GetReturnValue().Set(buf);
  311. return;
  312. }
  313. }
  314. }
  315. /*
  316. * Canvas::StreamPNG callback.
  317. */
  318. static cairo_status_t
  319. streamPNG(void *c, const uint8_t *data, unsigned len) {
  320. Nan::HandleScope scope;
  321. closure_t *closure = (closure_t *) c;
  322. Local<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
  323. Local<Value> argv[3] = {
  324. Nan::Null()
  325. , buf
  326. , Nan::New<Number>(len) };
  327. Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure->fn, 3, argv);
  328. return CAIRO_STATUS_SUCCESS;
  329. }
  330. /*
  331. * Stream PNG data synchronously.
  332. */
  333. NAN_METHOD(Canvas::StreamPNGSync) {
  334. uint32_t compression_level = 6;
  335. uint32_t filter = PNG_ALL_FILTERS;
  336. // TODO: async as well
  337. if (!info[0]->IsFunction())
  338. return Nan::ThrowTypeError("callback function required");
  339. if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
  340. if (!info[1]->IsUndefined()) {
  341. bool good = true;
  342. if (info[1]->IsNumber()) {
  343. compression_level = info[1]->Uint32Value();
  344. } else if (info[1]->IsString()) {
  345. if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
  346. compression_level = 0;
  347. } else {
  348. uint32_t tmp = info[1]->Uint32Value();
  349. if (tmp == 0) {
  350. good = false;
  351. } else {
  352. compression_level = tmp;
  353. }
  354. }
  355. } else {
  356. good = false;
  357. }
  358. if (good) {
  359. if (compression_level > 9) {
  360. return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
  361. }
  362. } else {
  363. return Nan::ThrowTypeError("Compression level must be a number.");
  364. }
  365. }
  366. if (!info[2]->IsUndefined()) {
  367. if (info[2]->IsUint32()) {
  368. filter = info[2]->Uint32Value();
  369. } else {
  370. return Nan::ThrowTypeError("Invalid filter value.");
  371. }
  372. }
  373. }
  374. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  375. closure_t closure;
  376. closure.fn = Local<Function>::Cast(info[0]);
  377. closure.compression_level = compression_level;
  378. closure.filter = filter;
  379. TryCatch try_catch;
  380. cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
  381. if (try_catch.HasCaught()) {
  382. try_catch.ReThrow();
  383. return;
  384. } else if (status) {
  385. Local<Value> argv[1] = { Canvas::Error(status) };
  386. Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
  387. } else {
  388. Local<Value> argv[3] = {
  389. Nan::Null()
  390. , Nan::Null()
  391. , Nan::New<Uint32>(0) };
  392. Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
  393. }
  394. return;
  395. }
  396. /*
  397. * Canvas::StreamPDF FreeCallback
  398. */
  399. void stream_pdf_free(char *, void *) {}
  400. /*
  401. * Canvas::StreamPDF callback.
  402. */
  403. static cairo_status_t
  404. streamPDF(void *c, const uint8_t *data, unsigned len) {
  405. Nan::HandleScope scope;
  406. closure_t *closure = static_cast<closure_t *>(c);
  407. Local<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
  408. Local<Value> argv[3] = {
  409. Nan::Null()
  410. , buf
  411. , Nan::New<Number>(len) };
  412. Nan::MakeCallback(Nan::GetCurrentContext()->Global(), closure->fn, 3, argv);
  413. return CAIRO_STATUS_SUCCESS;
  414. }
  415. cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, void *closure) {
  416. closure_t *pdf_closure = static_cast<closure_t *>(closure);
  417. size_t whole_chunks = pdf_closure->len / PAGE_SIZE;
  418. size_t remainder = pdf_closure->len - whole_chunks * PAGE_SIZE;
  419. for (size_t i = 0; i < whole_chunks; ++i) {
  420. write_func(pdf_closure, &pdf_closure->data[i * PAGE_SIZE], PAGE_SIZE);
  421. }
  422. if (remainder) {
  423. write_func(pdf_closure, &pdf_closure->data[whole_chunks * PAGE_SIZE], remainder);
  424. }
  425. return CAIRO_STATUS_SUCCESS;
  426. }
  427. /*
  428. * Stream PDF data synchronously.
  429. */
  430. NAN_METHOD(Canvas::StreamPDFSync) {
  431. if (!info[0]->IsFunction())
  432. return Nan::ThrowTypeError("callback function required");
  433. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());
  434. if (!canvas->isPDF())
  435. return Nan::ThrowTypeError("wrong canvas type");
  436. cairo_surface_finish(canvas->surface());
  437. closure_t closure;
  438. closure.data = static_cast<closure_t *>(canvas->closure())->data;
  439. closure.len = static_cast<closure_t *>(canvas->closure())->len;
  440. closure.fn = info[0].As<Function>();
  441. Nan::TryCatch try_catch;
  442. cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &closure);
  443. if (try_catch.HasCaught()) {
  444. try_catch.ReThrow();
  445. } else if (status) {
  446. Local<Value> error = Canvas::Error(status);
  447. Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 1, &error);
  448. } else {
  449. Local<Value> argv[3] = {
  450. Nan::Null()
  451. , Nan::Null()
  452. , Nan::New<Uint32>(0) };
  453. Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 3, argv);
  454. }
  455. }
  456. /*
  457. * Stream JPEG data synchronously.
  458. */
  459. #ifdef HAVE_JPEG
  460. NAN_METHOD(Canvas::StreamJPEGSync) {
  461. // TODO: async as well
  462. if (!info[0]->IsNumber())
  463. return Nan::ThrowTypeError("buffer size required");
  464. if (!info[1]->IsNumber())
  465. return Nan::ThrowTypeError("quality setting required");
  466. if (!info[2]->IsBoolean())
  467. return Nan::ThrowTypeError("progressive setting required");
  468. if (!info[3]->IsFunction())
  469. return Nan::ThrowTypeError("callback function required");
  470. Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
  471. closure_t closure;
  472. closure.fn = Local<Function>::Cast(info[3]);
  473. TryCatch try_catch;
  474. write_to_jpeg_stream(canvas->surface(), info[0]->NumberValue(), info[1]->NumberValue(), info[2]->BooleanValue(), &closure);
  475. if (try_catch.HasCaught()) {
  476. try_catch.ReThrow();
  477. }
  478. return;
  479. }
  480. #endif
  481. NAN_METHOD(Canvas::RegisterFont) {
  482. FontFace face;
  483. if (!info[0]->IsString()) {
  484. return Nan::ThrowError("Wrong argument type");
  485. }
  486. String::Utf8Value filePath(info[0]);
  487. if (!register_font((unsigned char*) *filePath, &face.target_desc)) {
  488. Nan::ThrowError("Could not load font to the system's font host");
  489. } else {
  490. PangoFontDescription* d = pango_font_description_new();
  491. if (!info[1]->IsObject()) {
  492. Nan::ThrowError(GENERIC_FACE_ERROR);
  493. } else { // now check the attrs, there are many ways to be wrong
  494. Local<Object> desc = info[1]->ToObject();
  495. Local<String> family_prop = Nan::New<String>("family").ToLocalChecked();
  496. Local<String> weight_prop = Nan::New<String>("weight").ToLocalChecked();
  497. Local<String> style_prop = Nan::New<String>("style").ToLocalChecked();
  498. const char* family;
  499. const char* weight = "normal";
  500. const char* style = "normal";
  501. Local<Value> family_val = desc->Get(family_prop);
  502. if (family_val->IsString()) {
  503. family = strdup(*String::Utf8Value(family_val));
  504. } else {
  505. Nan::ThrowError(GENERIC_FACE_ERROR);
  506. return;
  507. }
  508. if (desc->HasOwnProperty(weight_prop)) {
  509. Local<Value> weight_val = desc->Get(weight_prop);
  510. if (weight_val->IsString() || weight_val->IsNumber()) {
  511. weight = strdup(*String::Utf8Value(weight_val));
  512. } else {
  513. Nan::ThrowError(GENERIC_FACE_ERROR);
  514. return;
  515. }
  516. }
  517. if (desc->HasOwnProperty(style_prop)) {
  518. Local<Value> style_val = desc->Get(style_prop);
  519. if (style_val->IsString()) {
  520. style = strdup(*String::Utf8Value(style_val));
  521. } else {
  522. Nan::ThrowError(GENERIC_FACE_ERROR);
  523. return;
  524. }
  525. }
  526. pango_font_description_set_weight(d, Canvas::GetWeightFromCSSString(weight));
  527. pango_font_description_set_style(d, Canvas::GetStyleFromCSSString(style));
  528. pango_font_description_set_family(d, family);
  529. free((char*)family);
  530. if (desc->HasOwnProperty(weight_prop)) free((char*)weight);
  531. if (desc->HasOwnProperty(style_prop)) free((char*)style);
  532. face.user_desc = d;
  533. _font_face_list.push_back(face);
  534. }
  535. }
  536. }
  537. /*
  538. * Initialize cairo surface.
  539. */
  540. Canvas::Canvas(int w, int h, canvas_type_t t): Nan::ObjectWrap() {
  541. type = t;
  542. width = w;
  543. height = h;
  544. _surface = NULL;
  545. _closure = NULL;
  546. if (CANVAS_TYPE_PDF == t) {
  547. _closure = malloc(sizeof(closure_t));
  548. assert(_closure);
  549. cairo_status_t status = closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
  550. assert(status == CAIRO_STATUS_SUCCESS);
  551. _surface = cairo_pdf_surface_create_for_stream(toBuffer, _closure, w, h);
  552. } else if (CANVAS_TYPE_SVG == t) {
  553. _closure = malloc(sizeof(closure_t));
  554. assert(_closure);
  555. cairo_status_t status = closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
  556. assert(status == CAIRO_STATUS_SUCCESS);
  557. _surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, w, h);
  558. } else {
  559. _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
  560. assert(_surface);
  561. Nan::AdjustExternalMemory(nBytes());
  562. }
  563. }
  564. /*
  565. * Destroy cairo surface.
  566. */
  567. Canvas::~Canvas() {
  568. switch (type) {
  569. case CANVAS_TYPE_PDF:
  570. case CANVAS_TYPE_SVG:
  571. cairo_surface_finish(_surface);
  572. closure_destroy((closure_t *) _closure);
  573. free(_closure);
  574. cairo_surface_destroy(_surface);
  575. break;
  576. case CANVAS_TYPE_IMAGE:
  577. int oldNBytes = nBytes();
  578. cairo_surface_destroy(_surface);
  579. Nan::AdjustExternalMemory(-oldNBytes);
  580. break;
  581. }
  582. }
  583. std::vector<FontFace>
  584. _init_font_face_list() {
  585. std::vector<FontFace> x;
  586. return x;
  587. }
  588. std::vector<FontFace> Canvas::_font_face_list = _init_font_face_list();
  589. /*
  590. * Get a PangoStyle from a CSS string (like "italic")
  591. */
  592. PangoStyle
  593. Canvas::GetStyleFromCSSString(const char *style) {
  594. PangoStyle s = PANGO_STYLE_NORMAL;
  595. if (strlen(style) > 0) {
  596. if (0 == strcmp("italic", style)) {
  597. s = PANGO_STYLE_ITALIC;
  598. } else if (0 == strcmp("oblique", style)) {
  599. s = PANGO_STYLE_OBLIQUE;
  600. }
  601. }
  602. return s;
  603. }
  604. /*
  605. * Get a PangoWeight from a CSS string ("bold", "100", etc)
  606. */
  607. PangoWeight
  608. Canvas::GetWeightFromCSSString(const char *weight) {
  609. PangoWeight w = PANGO_WEIGHT_NORMAL;
  610. if (strlen(weight) > 0) {
  611. if (0 == strcmp("bold", weight)) {
  612. w = PANGO_WEIGHT_BOLD;
  613. } else if (0 == strcmp("100", weight)) {
  614. w = PANGO_WEIGHT_THIN;
  615. } else if (0 == strcmp("200", weight)) {
  616. w = PANGO_WEIGHT_ULTRALIGHT;
  617. } else if (0 == strcmp("300", weight)) {
  618. w = PANGO_WEIGHT_LIGHT;
  619. } else if (0 == strcmp("400", weight)) {
  620. w = PANGO_WEIGHT_NORMAL;
  621. } else if (0 == strcmp("500", weight)) {
  622. w = PANGO_WEIGHT_MEDIUM;
  623. } else if (0 == strcmp("600", weight)) {
  624. w = PANGO_WEIGHT_SEMIBOLD;
  625. } else if (0 == strcmp("700", weight)) {
  626. w = PANGO_WEIGHT_BOLD;
  627. } else if (0 == strcmp("800", weight)) {
  628. w = PANGO_WEIGHT_ULTRABOLD;
  629. } else if (0 == strcmp("900", weight)) {
  630. w = PANGO_WEIGHT_HEAVY;
  631. }
  632. }
  633. return w;
  634. }
  635. /*
  636. * Tries to find a matching font given to registerFont
  637. */
  638. PangoFontDescription *
  639. Canvas::FindCustomFace(PangoFontDescription *desc) {
  640. PangoFontDescription* best_match = NULL;
  641. PangoFontDescription* best_match_target = NULL;
  642. std::vector<FontFace>::iterator it = _font_face_list.begin();
  643. while (it != _font_face_list.end()) {
  644. FontFace f = *it;
  645. if (g_ascii_strcasecmp(pango_font_description_get_family(desc),
  646. pango_font_description_get_family(f.user_desc)) == 0) {
  647. if (best_match == NULL || pango_font_description_better_match(desc, best_match, f.user_desc)) {
  648. best_match = f.user_desc;
  649. best_match_target = f.target_desc;
  650. }
  651. }
  652. ++it;
  653. }
  654. return best_match_target;
  655. }
  656. /*
  657. * Re-alloc the surface, destroying the previous.
  658. */
  659. void
  660. Canvas::resurface(Local<Object> canvas) {
  661. Nan::HandleScope scope;
  662. Local<Value> context;
  663. switch (type) {
  664. case CANVAS_TYPE_PDF:
  665. cairo_pdf_surface_set_size(_surface, width, height);
  666. break;
  667. case CANVAS_TYPE_SVG:
  668. // Re-surface
  669. cairo_surface_finish(_surface);
  670. closure_destroy((closure_t *) _closure);
  671. cairo_surface_destroy(_surface);
  672. closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
  673. _surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, width, height);
  674. // Reset context
  675. context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
  676. if (!context->IsUndefined()) {
  677. Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
  678. cairo_t *prev = context2d->context();
  679. context2d->setContext(cairo_create(surface()));
  680. cairo_destroy(prev);
  681. }
  682. break;
  683. case CANVAS_TYPE_IMAGE:
  684. // Re-surface
  685. size_t oldNBytes = nBytes();
  686. cairo_surface_destroy(_surface);
  687. _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  688. Nan::AdjustExternalMemory(nBytes() - oldNBytes);
  689. // Reset context
  690. context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
  691. if (!context->IsUndefined()) {
  692. Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
  693. cairo_t *prev = context2d->context();
  694. context2d->setContext(cairo_create(surface()));
  695. cairo_destroy(prev);
  696. }
  697. break;
  698. }
  699. }
  700. /*
  701. * Construct an Error from the given cairo status.
  702. */
  703. Local<Value>
  704. Canvas::Error(cairo_status_t status) {
  705. return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
  706. }