支持 mac

This commit is contained in:
onvia
2023-07-24 11:13:08 +08:00
parent 413e79966a
commit 516a7f20c1
1940 changed files with 693119 additions and 1178 deletions

View File

@@ -0,0 +1,18 @@
#include "Backends.h"
#include "backend/ImageBackend.h"
#include "backend/PdfBackend.h"
#include "backend/SvgBackend.h"
using namespace v8;
void Backends::Initialize(Local<Object> target) {
Nan::HandleScope scope;
Local<Object> obj = Nan::New<Object>();
ImageBackend::Initialize(obj);
PdfBackend::Initialize(obj);
SvgBackend::Initialize(obj);
Nan::Set(target, Nan::New<String>("Backends").ToLocalChecked(), obj).Check();
}

View File

@@ -0,0 +1,10 @@
#pragma once
#include "backend/Backend.h"
#include <nan.h>
#include <v8.h>
class Backends : public Nan::ObjectWrap {
public:
static void Initialize(v8::Local<v8::Object> target);
};

View File

@@ -0,0 +1,965 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "Canvas.h"
#include <algorithm> // std::min
#include <assert.h>
#include <cairo-pdf.h>
#include <cairo-svg.h>
#include "CanvasRenderingContext2d.h"
#include "closure.h"
#include <cstring>
#include <cctype>
#include <ctime>
#include <glib.h>
#include "PNG.h"
#include "register_font.h"
#include <sstream>
#include <stdlib.h>
#include <string>
#include <unordered_set>
#include "Util.h"
#include <vector>
#include "node_buffer.h"
#ifdef HAVE_JPEG
#include "JPEGStream.h"
#endif
#include "backend/ImageBackend.h"
#include "backend/PdfBackend.h"
#include "backend/SvgBackend.h"
#define GENERIC_FACE_ERROR \
"The second argument to registerFont is required, and should be an object " \
"with at least a family (string) and optionally weight (string/number) " \
"and style (string)."
#define CHECK_RECEIVER(prop) \
if (!Canvas::constructor.Get(info.GetIsolate())->HasInstance(info.This())) { \
Nan::ThrowTypeError("Method " #prop " called on incompatible receiver"); \
return; \
}
using namespace v8;
using namespace std;
Nan::Persistent<FunctionTemplate> Canvas::constructor;
std::vector<FontFace> font_face_list;
/*
* Initialize Canvas.
*/
void
Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
// Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
#ifdef HAVE_JPEG
Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
#endif
Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride);
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New<Uint32>(PNG_NO_FILTERS));
Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
// Class methods
Nan::SetMethod(ctor, "_registerFont", RegisterFont);
Nan::SetMethod(ctor, "_deregisterAllFonts", DeregisterAllFonts);
Local<Context> ctx = Nan::GetCurrentContext();
Nan::Set(target,
Nan::New("Canvas").ToLocalChecked(),
ctor->GetFunction(ctx).ToLocalChecked());
}
/*
* Initialize a Canvas with the given width and height.
*/
NAN_METHOD(Canvas::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
}
Backend* backend = NULL;
if (info[0]->IsNumber()) {
int width = Nan::To<uint32_t>(info[0]).FromMaybe(0), height = 0;
if (info[1]->IsNumber()) height = Nan::To<uint32_t>(info[1]).FromMaybe(0);
if (info[2]->IsString()) {
if (0 == strcmp("pdf", *Nan::Utf8String(info[2])))
backend = new PdfBackend(width, height);
else if (0 == strcmp("svg", *Nan::Utf8String(info[2])))
backend = new SvgBackend(width, height);
else
backend = new ImageBackend(width, height);
}
else
backend = new ImageBackend(width, height);
}
else if (info[0]->IsObject()) {
if (Nan::New(ImageBackend::constructor)->HasInstance(info[0]) ||
Nan::New(PdfBackend::constructor)->HasInstance(info[0]) ||
Nan::New(SvgBackend::constructor)->HasInstance(info[0])) {
backend = Nan::ObjectWrap::Unwrap<Backend>(Nan::To<Object>(info[0]).ToLocalChecked());
}else{
return Nan::ThrowTypeError("Invalid arguments");
}
}
else {
backend = new ImageBackend(0, 0);
}
if (!backend->isSurfaceValid()) {
delete backend;
return Nan::ThrowError(backend->getError());
}
Canvas* canvas = new Canvas(backend);
canvas->Wrap(info.This());
backend->setCanvas(canvas);
info.GetReturnValue().Set(info.This());
}
/*
* Get type string.
*/
NAN_GETTER(Canvas::GetType) {
CHECK_RECEIVER(Canvas.GetType);
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<String>(canvas->backend()->getName()).ToLocalChecked());
}
/*
* Get stride.
*/
NAN_GETTER(Canvas::GetStride) {
CHECK_RECEIVER(Canvas.GetStride);
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
}
/*
* Get width.
*/
NAN_GETTER(Canvas::GetWidth) {
CHECK_RECEIVER(Canvas.GetWidth);
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->getWidth()));
}
/*
* Set width.
*/
NAN_SETTER(Canvas::SetWidth) {
CHECK_RECEIVER(Canvas.SetWidth);
if (value->IsNumber()) {
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
canvas->backend()->setWidth(Nan::To<uint32_t>(value).FromMaybe(0));
canvas->resurface(info.This());
}
}
/*
* Get height.
*/
NAN_GETTER(Canvas::GetHeight) {
CHECK_RECEIVER(Canvas.GetHeight);
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->getHeight()));
}
/*
* Set height.
*/
NAN_SETTER(Canvas::SetHeight) {
CHECK_RECEIVER(Canvas.SetHeight);
if (value->IsNumber()) {
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
canvas->backend()->setHeight(Nan::To<uint32_t>(value).FromMaybe(0));
canvas->resurface(info.This());
}
}
/*
* EIO toBuffer callback.
*/
void
Canvas::ToPngBufferAsync(uv_work_t *req) {
PngClosure* closure = static_cast<PngClosure*>(req->data);
closure->status = canvas_write_to_png_stream(
closure->canvas->surface(),
PngClosure::writeVec,
closure);
}
#ifdef HAVE_JPEG
void
Canvas::ToJpegBufferAsync(uv_work_t *req) {
JpegClosure* closure = static_cast<JpegClosure*>(req->data);
write_to_jpeg_buffer(closure->canvas->surface(), closure);
}
#endif
/*
* EIO after toBuffer callback.
*/
void
Canvas::ToBufferAsyncAfter(uv_work_t *req) {
Nan::HandleScope scope;
Nan::AsyncResource async("canvas:ToBufferAsyncAfter");
Closure* closure = static_cast<Closure*>(req->data);
delete req;
if (closure->status) {
Local<Value> argv[1] = { Canvas::Error(closure->status) };
closure->cb.Call(1, argv, &async);
} else {
Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
Local<Value> argv[2] = { Nan::Null(), buf };
closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
}
closure->canvas->Unref();
delete closure;
}
static void parsePNGArgs(Local<Value> arg, PngClosure& pngargs) {
if (arg->IsObject()) {
Local<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
Local<Value> cLevel = Nan::Get(obj, Nan::New("compressionLevel").ToLocalChecked()).ToLocalChecked();
if (cLevel->IsUint32()) {
uint32_t val = Nan::To<uint32_t>(cLevel).FromMaybe(0);
// See quote below from spec section 4.12.5.5.
if (val <= 9) pngargs.compressionLevel = val;
}
Local<Value> rez = Nan::Get(obj, Nan::New("resolution").ToLocalChecked()).ToLocalChecked();
if (rez->IsUint32()) {
uint32_t val = Nan::To<uint32_t>(rez).FromMaybe(0);
if (val > 0) pngargs.resolution = val;
}
Local<Value> filters = Nan::Get(obj, Nan::New("filters").ToLocalChecked()).ToLocalChecked();
if (filters->IsUint32()) pngargs.filters = Nan::To<uint32_t>(filters).FromMaybe(0);
Local<Value> palette = Nan::Get(obj, Nan::New("palette").ToLocalChecked()).ToLocalChecked();
if (palette->IsUint8ClampedArray()) {
Local<Uint8ClampedArray> palette_ta = palette.As<Uint8ClampedArray>();
pngargs.nPaletteColors = palette_ta->Length();
if (pngargs.nPaletteColors % 4 != 0) {
throw "Palette length must be a multiple of 4.";
}
pngargs.nPaletteColors /= 4;
Nan::TypedArrayContents<uint8_t> _paletteColors(palette_ta);
pngargs.palette = *_paletteColors;
// Optional background color index:
Local<Value> backgroundIndexVal = Nan::Get(obj, Nan::New("backgroundIndex").ToLocalChecked()).ToLocalChecked();
if (backgroundIndexVal->IsUint32()) {
pngargs.backgroundIndex = static_cast<uint8_t>(Nan::To<uint32_t>(backgroundIndexVal).FromMaybe(0));
}
}
}
}
#ifdef HAVE_JPEG
static void parseJPEGArgs(Local<Value> arg, JpegClosure& jpegargs) {
// "If Type(quality) is not Number, or if quality is outside that range, the
// user agent must use its default quality value, as if the quality argument
// had not been given." - 4.12.5.5
if (arg->IsObject()) {
Local<Object> obj = Nan::To<Object>(arg).ToLocalChecked();
Local<Value> qual = Nan::Get(obj, Nan::New("quality").ToLocalChecked()).ToLocalChecked();
if (qual->IsNumber()) {
double quality = Nan::To<double>(qual).FromMaybe(0);
if (quality >= 0.0 && quality <= 1.0) {
jpegargs.quality = static_cast<uint32_t>(100.0 * quality);
}
}
Local<Value> chroma = Nan::Get(obj, Nan::New("chromaSubsampling").ToLocalChecked()).ToLocalChecked();
if (chroma->IsBoolean()) {
bool subsample = Nan::To<bool>(chroma).FromMaybe(0);
jpegargs.chromaSubsampling = subsample ? 2 : 1;
} else if (chroma->IsNumber()) {
jpegargs.chromaSubsampling = Nan::To<uint32_t>(chroma).FromMaybe(0);
}
Local<Value> progressive = Nan::Get(obj, Nan::New("progressive").ToLocalChecked()).ToLocalChecked();
if (!progressive->IsUndefined()) {
jpegargs.progressive = Nan::To<bool>(progressive).FromMaybe(0);
}
}
}
#endif
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
static inline void setPdfMetaStr(cairo_surface_t* surf, Local<Object> opts,
cairo_pdf_metadata_t t, const char* pName) {
auto propName = Nan::New(pName).ToLocalChecked();
auto propValue = Nan::Get(opts, propName).ToLocalChecked();
if (propValue->IsString()) {
// (copies char data)
cairo_pdf_surface_set_metadata(surf, t, *Nan::Utf8String(propValue));
}
}
static inline void setPdfMetaDate(cairo_surface_t* surf, Local<Object> opts,
cairo_pdf_metadata_t t, const char* pName) {
auto propName = Nan::New(pName).ToLocalChecked();
auto propValue = Nan::Get(opts, propName).ToLocalChecked();
if (propValue->IsDate()) {
auto date = static_cast<time_t>(propValue.As<v8::Date>()->ValueOf() / 1000); // ms -> s
char buf[sizeof "2011-10-08T07:07:09Z"];
strftime(buf, sizeof buf, "%FT%TZ", gmtime(&date));
cairo_pdf_surface_set_metadata(surf, t, buf);
}
}
static void setPdfMetadata(Canvas* canvas, Local<Object> opts) {
cairo_surface_t* surf = canvas->surface();
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_TITLE, "title");
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_AUTHOR, "author");
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_SUBJECT, "subject");
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_KEYWORDS, "keywords");
setPdfMetaStr(surf, opts, CAIRO_PDF_METADATA_CREATOR, "creator");
setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_CREATE_DATE, "creationDate");
setPdfMetaDate(surf, opts, CAIRO_PDF_METADATA_MOD_DATE, "modDate");
}
#endif // CAIRO 16+
/*
* Converts/encodes data to a Buffer. Async when a callback function is passed.
* PDF canvases:
(any) => Buffer
("application/pdf", config) => Buffer
* SVG canvases:
(any) => Buffer
* ARGB data:
("raw") => Buffer
* PNG-encoded
() => Buffer
(undefined|"image/png", {compressionLevel?: number, filter?: number}) => Buffer
((err: null|Error, buffer) => any)
((err: null|Error, buffer) => any, undefined|"image/png", {compressionLevel?: number, filter?: number})
* JPEG-encoded
("image/jpeg") => Buffer
("image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number}) => Buffer
((err: null|Error, buffer) => any, "image/jpeg")
((err: null|Error, buffer) => any, "image/jpeg", {quality?: number, progressive?: Boolean, chromaSubsampling?: Boolean|number})
*/
NAN_METHOD(Canvas::ToBuffer) {
cairo_status_t status;
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
// Vector canvases, sync only
const std::string name = canvas->backend()->getName();
if (name == "pdf" || name == "svg") {
// mime type may be present, but it's not checked
PdfSvgClosure* closure;
if (name == "pdf") {
closure = static_cast<PdfBackend*>(canvas->backend())->closure();
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
if (info[1]->IsObject()) { // toBuffer("application/pdf", config)
setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
}
#endif // CAIRO 16+
} else {
closure = static_cast<SvgBackend*>(canvas->backend())->closure();
}
cairo_surface_finish(canvas->surface());
Local<Object> buf = Nan::CopyBuffer((char*)&closure->vec[0], closure->vec.size()).ToLocalChecked();
info.GetReturnValue().Set(buf);
return;
}
// Raw ARGB data -- just a memcpy()
if (info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
cairo_surface_t *surface = canvas->surface();
cairo_surface_flush(surface);
if (canvas->nBytes() > node::Buffer::kMaxLength) {
Nan::ThrowError("Data exceeds maximum buffer length.");
return;
}
const unsigned char *data = cairo_image_surface_get_data(surface);
Isolate* iso = Nan::GetCurrentContext()->GetIsolate();
Local<Object> buf = node::Buffer::Copy(iso, reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
info.GetReturnValue().Set(buf);
return;
}
// Sync PNG, default
if (info[0]->IsUndefined() || info[0]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked())) {
try {
PngClosure closure(canvas);
parsePNGArgs(info[1], closure);
if (closure.nPaletteColors == 0xFFFFFFFF) {
Nan::ThrowError("Palette length must be a multiple of 4.");
return;
}
Nan::TryCatch try_catch;
status = canvas_write_to_png_stream(canvas->surface(), PngClosure::writeVec, &closure);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
} else if (status) {
throw status;
} else {
// TODO it's possible to avoid this copy
Local<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
info.GetReturnValue().Set(buf);
}
} catch (cairo_status_t ex) {
Nan::ThrowError(Canvas::Error(ex));
} catch (const char* ex) {
Nan::ThrowError(ex);
}
return;
}
// Async PNG
if (info[0]->IsFunction() &&
(info[1]->IsUndefined() || info[1]->StrictEquals(Nan::New<String>("image/png").ToLocalChecked()))) {
PngClosure* closure;
try {
closure = new PngClosure(canvas);
parsePNGArgs(info[2], *closure);
} catch (cairo_status_t ex) {
Nan::ThrowError(Canvas::Error(ex));
return;
} catch (const char* ex) {
Nan::ThrowError(ex);
return;
}
canvas->Ref();
closure->cb.Reset(info[0].As<Function>());
uv_work_t* req = new uv_work_t;
req->data = closure;
// Make sure the surface exists since we won't have an isolate context in the async block:
canvas->surface();
uv_queue_work(uv_default_loop(), req, ToPngBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
return;
}
#ifdef HAVE_JPEG
// Sync JPEG
Local<Value> jpegStr = Nan::New<String>("image/jpeg").ToLocalChecked();
if (info[0]->StrictEquals(jpegStr)) {
try {
JpegClosure closure(canvas);
parseJPEGArgs(info[1], closure);
Nan::TryCatch try_catch;
write_to_jpeg_buffer(canvas->surface(), &closure);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
} else {
// TODO it's possible to avoid this copy.
Local<Object> buf = Nan::CopyBuffer((char *)&closure.vec[0], closure.vec.size()).ToLocalChecked();
info.GetReturnValue().Set(buf);
}
} catch (cairo_status_t ex) {
Nan::ThrowError(Canvas::Error(ex));
}
return;
}
// Async JPEG
if (info[0]->IsFunction() && info[1]->StrictEquals(jpegStr)) {
JpegClosure* closure = new JpegClosure(canvas);
parseJPEGArgs(info[2], *closure);
canvas->Ref();
closure->cb.Reset(info[0].As<Function>());
uv_work_t* req = new uv_work_t;
req->data = closure;
// Make sure the surface exists since we won't have an isolate context in the async block:
canvas->surface();
uv_queue_work(uv_default_loop(), req, ToJpegBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
return;
}
#endif
}
/*
* Canvas::StreamPNG callback.
*/
static cairo_status_t
streamPNG(void *c, const uint8_t *data, unsigned len) {
Nan::HandleScope scope;
Nan::AsyncResource async("canvas:StreamPNG");
PngClosure* closure = (PngClosure*) c;
Local<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
Local<Value> argv[3] = {
Nan::Null()
, buf
, Nan::New<Number>(len) };
closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
return CAIRO_STATUS_SUCCESS;
}
/*
* Stream PNG data synchronously. TODO async
* StreamPngSync(this, options: {palette?: Uint8ClampedArray, backgroundIndex?: uint32, compressionLevel: uint32, filters: uint32})
*/
NAN_METHOD(Canvas::StreamPNGSync) {
if (!info[0]->IsFunction())
return Nan::ThrowTypeError("callback function required");
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
PngClosure closure(canvas);
parsePNGArgs(info[1], closure);
closure.cb.Reset(Local<Function>::Cast(info[0]));
Nan::TryCatch try_catch;
cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
return;
} else if (status) {
Local<Value> argv[1] = { Canvas::Error(status) };
Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
} else {
Local<Value> argv[3] = {
Nan::Null()
, Nan::Null()
, Nan::New<Uint32>(0) };
Nan::Call(closure.cb, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
}
return;
}
struct PdfStreamInfo {
Local<Function> fn;
uint32_t len;
uint8_t* data;
};
/*
* Canvas::StreamPDF FreeCallback
*/
void stream_pdf_free(char *, void *) {}
/*
* Canvas::StreamPDF callback.
*/
static cairo_status_t
streamPDF(void *c, const uint8_t *data, unsigned len) {
Nan::HandleScope scope;
Nan::AsyncResource async("canvas:StreamPDF");
PdfStreamInfo* streaminfo = static_cast<PdfStreamInfo*>(c);
// TODO this is technically wrong, we're returning a pointer to the data in a
// vector in a class with automatic storage duration. If the canvas goes out
// of scope while we're in the handler, a use-after-free could happen.
Local<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
Local<Value> argv[3] = {
Nan::Null()
, buf
, Nan::New<Number>(len) };
async.runInAsyncScope(Nan::GetCurrentContext()->Global(), streaminfo->fn, sizeof argv / sizeof *argv, argv);
return CAIRO_STATUS_SUCCESS;
}
cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PdfStreamInfo* streaminfo) {
size_t whole_chunks = streaminfo->len / PAGE_SIZE;
size_t remainder = streaminfo->len - whole_chunks * PAGE_SIZE;
for (size_t i = 0; i < whole_chunks; ++i) {
write_func(streaminfo, &streaminfo->data[i * PAGE_SIZE], PAGE_SIZE);
}
if (remainder) {
write_func(streaminfo, &streaminfo->data[whole_chunks * PAGE_SIZE], remainder);
}
return CAIRO_STATUS_SUCCESS;
}
/*
* Stream PDF data synchronously.
*/
NAN_METHOD(Canvas::StreamPDFSync) {
if (!info[0]->IsFunction())
return Nan::ThrowTypeError("callback function required");
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());
if (canvas->backend()->getName() != "pdf")
return Nan::ThrowTypeError("wrong canvas type");
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
if (info[1]->IsObject()) {
setPdfMetadata(canvas, Nan::To<Object>(info[1]).ToLocalChecked());
}
#endif
cairo_surface_finish(canvas->surface());
PdfSvgClosure* closure = static_cast<PdfBackend*>(canvas->backend())->closure();
Local<Function> fn = info[0].As<Function>();
PdfStreamInfo streaminfo;
streaminfo.fn = fn;
streaminfo.data = &closure->vec[0];
streaminfo.len = closure->vec.size();
Nan::TryCatch try_catch;
cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &streaminfo);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
} else if (status) {
Local<Value> error = Canvas::Error(status);
Nan::Call(fn, Nan::GetCurrentContext()->Global(), 1, &error);
} else {
Local<Value> argv[3] = {
Nan::Null()
, Nan::Null()
, Nan::New<Uint32>(0) };
Nan::Call(fn, Nan::GetCurrentContext()->Global(), sizeof argv / sizeof *argv, argv);
}
}
/*
* Stream JPEG data synchronously.
*/
#ifdef HAVE_JPEG
static uint32_t getSafeBufSize(Canvas* canvas) {
// Don't allow the buffer size to exceed the size of the canvas (#674)
// TODO not sure if this is really correct, but it fixed #674
return (std::min)(canvas->getWidth() * canvas->getHeight() * 4, static_cast<int>(PAGE_SIZE));
}
NAN_METHOD(Canvas::StreamJPEGSync) {
if (!info[1]->IsFunction())
return Nan::ThrowTypeError("callback function required");
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
JpegClosure closure(canvas);
parseJPEGArgs(info[0], closure);
closure.cb.Reset(Local<Function>::Cast(info[1]));
Nan::TryCatch try_catch;
uint32_t bufsize = getSafeBufSize(canvas);
write_to_jpeg_stream(canvas->surface(), bufsize, &closure);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
}
return;
}
#endif
char *
str_value(Local<Value> val, const char *fallback, bool can_be_number) {
if (val->IsString() || (can_be_number && val->IsNumber())) {
return strdup(*Nan::Utf8String(val));
} else if (fallback) {
return strdup(fallback);
} else {
return NULL;
}
}
NAN_METHOD(Canvas::RegisterFont) {
if (!info[0]->IsString()) {
return Nan::ThrowError("Wrong argument type");
} else if (!info[1]->IsObject()) {
return Nan::ThrowError(GENERIC_FACE_ERROR);
}
Nan::Utf8String filePath(info[0]);
PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *) *filePath);
if (!sys_desc) return Nan::ThrowError("Could not parse font file");
PangoFontDescription *user_desc = pango_font_description_new();
// now check the attrs, there are many ways to be wrong
Local<Object> js_user_desc = Nan::To<Object>(info[1]).ToLocalChecked();
Local<String> family_prop = Nan::New<String>("family").ToLocalChecked();
Local<String> weight_prop = Nan::New<String>("weight").ToLocalChecked();
Local<String> style_prop = Nan::New<String>("style").ToLocalChecked();
char *family = str_value(Nan::Get(js_user_desc, family_prop).ToLocalChecked(), NULL, false);
char *weight = str_value(Nan::Get(js_user_desc, weight_prop).ToLocalChecked(), "normal", true);
char *style = str_value(Nan::Get(js_user_desc, style_prop).ToLocalChecked(), "normal", false);
if (family && weight && style) {
pango_font_description_set_weight(user_desc, Canvas::GetWeightFromCSSString(weight));
pango_font_description_set_style(user_desc, Canvas::GetStyleFromCSSString(style));
pango_font_description_set_family(user_desc, family);
auto found = std::find_if(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
return pango_font_description_equal(f.sys_desc, sys_desc);
});
if (found != font_face_list.end()) {
pango_font_description_free(found->user_desc);
found->user_desc = user_desc;
} else if (register_font((unsigned char *) *filePath)) {
FontFace face;
face.user_desc = user_desc;
face.sys_desc = sys_desc;
strncpy((char *)face.file_path, (char *) *filePath, 1023);
font_face_list.push_back(face);
} else {
pango_font_description_free(user_desc);
Nan::ThrowError("Could not load font to the system's font host");
}
} else {
pango_font_description_free(user_desc);
Nan::ThrowError(GENERIC_FACE_ERROR);
}
free(family);
free(weight);
free(style);
}
NAN_METHOD(Canvas::DeregisterAllFonts) {
// Unload all fonts from pango to free up memory
bool success = true;
std::for_each(font_face_list.begin(), font_face_list.end(), [&](FontFace& f) {
if (!deregister_font( (unsigned char *)f.file_path )) success = false;
pango_font_description_free(f.user_desc);
pango_font_description_free(f.sys_desc);
});
font_face_list.clear();
if (!success) Nan::ThrowError("Could not deregister one or more fonts");
}
/*
* Initialize cairo surface.
*/
Canvas::Canvas(Backend* backend) : ObjectWrap() {
_backend = backend;
}
/*
* Destroy cairo surface.
*/
Canvas::~Canvas() {
if (_backend != NULL) {
delete _backend;
}
}
/*
* Get a PangoStyle from a CSS string (like "italic")
*/
PangoStyle
Canvas::GetStyleFromCSSString(const char *style) {
PangoStyle s = PANGO_STYLE_NORMAL;
if (strlen(style) > 0) {
if (0 == strcmp("italic", style)) {
s = PANGO_STYLE_ITALIC;
} else if (0 == strcmp("oblique", style)) {
s = PANGO_STYLE_OBLIQUE;
}
}
return s;
}
/*
* Get a PangoWeight from a CSS string ("bold", "100", etc)
*/
PangoWeight
Canvas::GetWeightFromCSSString(const char *weight) {
PangoWeight w = PANGO_WEIGHT_NORMAL;
if (strlen(weight) > 0) {
if (0 == strcmp("bold", weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("100", weight)) {
w = PANGO_WEIGHT_THIN;
} else if (0 == strcmp("200", weight)) {
w = PANGO_WEIGHT_ULTRALIGHT;
} else if (0 == strcmp("300", weight)) {
w = PANGO_WEIGHT_LIGHT;
} else if (0 == strcmp("400", weight)) {
w = PANGO_WEIGHT_NORMAL;
} else if (0 == strcmp("500", weight)) {
w = PANGO_WEIGHT_MEDIUM;
} else if (0 == strcmp("600", weight)) {
w = PANGO_WEIGHT_SEMIBOLD;
} else if (0 == strcmp("700", weight)) {
w = PANGO_WEIGHT_BOLD;
} else if (0 == strcmp("800", weight)) {
w = PANGO_WEIGHT_ULTRABOLD;
} else if (0 == strcmp("900", weight)) {
w = PANGO_WEIGHT_HEAVY;
}
}
return w;
}
/*
* Given a user description, return a description that will select the
* font either from the system or @font-face
*/
PangoFontDescription *
Canvas::ResolveFontDescription(const PangoFontDescription *desc) {
// One of the user-specified families could map to multiple SFNT family names
// if someone registered two different fonts under the same family name.
// https://drafts.csswg.org/css-fonts-3/#font-style-matching
FontFace best;
istringstream families(pango_font_description_get_family(desc));
unordered_set<string> seen_families;
string resolved_families;
bool first = true;
for (string family; getline(families, family, ','); ) {
string renamed_families;
for (auto& ff : font_face_list) {
string pangofamily = string(pango_font_description_get_family(ff.user_desc));
if (streq_casein(family, pangofamily)) {
const char* sys_desc_family_name = pango_font_description_get_family(ff.sys_desc);
bool unseen = seen_families.find(sys_desc_family_name) == seen_families.end();
bool better = best.user_desc == nullptr || pango_font_description_better_match(desc, best.user_desc, ff.user_desc);
// Avoid sending duplicate SFNT font names due to a bug in Pango for macOS:
// https://bugzilla.gnome.org/show_bug.cgi?id=762873
if (unseen) {
seen_families.insert(sys_desc_family_name);
if (better) {
renamed_families = string(sys_desc_family_name) + (renamed_families.size() ? "," : "") + renamed_families;
} else {
renamed_families = renamed_families + (renamed_families.size() ? "," : "") + sys_desc_family_name;
}
}
if (first && better) best = ff;
}
}
if (resolved_families.size()) resolved_families += ',';
resolved_families += renamed_families.size() ? renamed_families : family;
first = false;
}
PangoFontDescription* ret = pango_font_description_copy(best.sys_desc ? best.sys_desc : desc);
pango_font_description_set_family(ret, resolved_families.c_str());
return ret;
}
/*
* Re-alloc the surface, destroying the previous.
*/
void
Canvas::resurface(Local<Object> canvas) {
Nan::HandleScope scope;
Local<Value> context;
backend()->recreateSurface();
// Reset context
context = Nan::Get(canvas, Nan::New<String>("context").ToLocalChecked()).ToLocalChecked();
if (!context->IsUndefined()) {
Context2d *context2d = ObjectWrap::Unwrap<Context2d>(Nan::To<Object>(context).ToLocalChecked());
cairo_t *prev = context2d->context();
context2d->setContext(createCairoContext());
context2d->resetState();
cairo_destroy(prev);
}
}
/**
* Wrapper around cairo_create()
* (do not call cairo_create directly, call this instead)
*/
cairo_t*
Canvas::createCairoContext() {
cairo_t* ret = cairo_create(surface());
cairo_set_line_width(ret, 1); // Cairo defaults to 2
return ret;
}
/*
* Construct an Error from the given cairo status.
*/
Local<Value>
Canvas::Error(cairo_status_t status) {
return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
}
#undef CHECK_RECEIVER

View File

@@ -0,0 +1,96 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "backend/Backend.h"
#include <cairo.h>
#include "dll_visibility.h"
#include <nan.h>
#include <pango/pangocairo.h>
#include <v8.h>
#include <vector>
#include <cstddef>
/*
* FontFace describes a font file in terms of one PangoFontDescription that
* will resolve to it and one that the user describes it as (like @font-face)
*/
class FontFace {
public:
PangoFontDescription *sys_desc = nullptr;
PangoFontDescription *user_desc = nullptr;
unsigned char file_path[1024];
};
enum text_baseline_t : uint8_t {
TEXT_BASELINE_ALPHABETIC = 0,
TEXT_BASELINE_TOP = 1,
TEXT_BASELINE_BOTTOM = 2,
TEXT_BASELINE_MIDDLE = 3,
TEXT_BASELINE_IDEOGRAPHIC = 4,
TEXT_BASELINE_HANGING = 5
};
enum text_align_t : int8_t {
TEXT_ALIGNMENT_LEFT = -1,
TEXT_ALIGNMENT_CENTER = 0,
TEXT_ALIGNMENT_RIGHT = 1,
// Currently same as LEFT and RIGHT without RTL support:
TEXT_ALIGNMENT_START = -2,
TEXT_ALIGNMENT_END = 2
};
enum canvas_draw_mode_t : uint8_t {
TEXT_DRAW_PATHS,
TEXT_DRAW_GLYPHS
};
/*
* Canvas.
*/
class Canvas: public Nan::ObjectWrap {
public:
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(ToBuffer);
static NAN_GETTER(GetType);
static NAN_GETTER(GetStride);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
static NAN_SETTER(SetWidth);
static NAN_SETTER(SetHeight);
static NAN_METHOD(StreamPNGSync);
static NAN_METHOD(StreamPDFSync);
static NAN_METHOD(StreamJPEGSync);
static NAN_METHOD(RegisterFont);
static NAN_METHOD(DeregisterAllFonts);
static v8::Local<v8::Value> Error(cairo_status_t status);
static void ToPngBufferAsync(uv_work_t *req);
static void ToJpegBufferAsync(uv_work_t *req);
static void ToBufferAsyncAfter(uv_work_t *req);
static PangoWeight GetWeightFromCSSString(const char *weight);
static PangoStyle GetStyleFromCSSString(const char *style);
static PangoFontDescription *ResolveFontDescription(const PangoFontDescription *desc);
DLL_PUBLIC inline Backend* backend() { return _backend; }
DLL_PUBLIC inline cairo_surface_t* surface(){ return backend()->getSurface(); }
cairo_t* createCairoContext();
DLL_PUBLIC inline uint8_t *data(){ return cairo_image_surface_get_data(surface()); }
DLL_PUBLIC inline int stride(){ return cairo_image_surface_get_stride(surface()); }
DLL_PUBLIC inline std::size_t nBytes(){
return static_cast<std::size_t>(getHeight()) * stride();
}
DLL_PUBLIC inline int getWidth() { return backend()->getWidth(); }
DLL_PUBLIC inline int getHeight() { return backend()->getHeight(); }
Canvas(Backend* backend);
void resurface(v8::Local<v8::Object> canvas);
private:
~Canvas();
Backend* _backend;
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include <string>
class CanvasError {
public:
std::string message;
std::string syscall;
std::string path;
int cerrno = 0;
void set(const char* iMessage = NULL, const char* iSyscall = NULL, int iErrno = 0, const char* iPath = NULL) {
if (iMessage) message.assign(iMessage);
if (iSyscall) syscall.assign(iSyscall);
cerrno = iErrno;
if (iPath) path.assign(iPath);
}
void reset() {
message.clear();
syscall.clear();
path.clear();
cerrno = 0;
}
};

View File

@@ -0,0 +1,123 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasGradient.h"
#include "Canvas.h"
#include "color.h"
using namespace v8;
Nan::Persistent<FunctionTemplate> Gradient::constructor;
/*
* Initialize CanvasGradient.
*/
void
Gradient::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Gradient::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New("CanvasGradient").ToLocalChecked());
// Prototype
Nan::SetPrototypeMethod(ctor, "addColorStop", AddColorStop);
Local<Context> ctx = Nan::GetCurrentContext();
Nan::Set(target,
Nan::New("CanvasGradient").ToLocalChecked(),
ctor->GetFunction(ctx).ToLocalChecked());
}
/*
* Initialize a new CanvasGradient.
*/
NAN_METHOD(Gradient::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
}
// Linear
if (4 == info.Length()) {
Gradient *grad = new Gradient(
Nan::To<double>(info[0]).FromMaybe(0)
, Nan::To<double>(info[1]).FromMaybe(0)
, Nan::To<double>(info[2]).FromMaybe(0)
, Nan::To<double>(info[3]).FromMaybe(0));
grad->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
// Radial
if (6 == info.Length()) {
Gradient *grad = new Gradient(
Nan::To<double>(info[0]).FromMaybe(0)
, Nan::To<double>(info[1]).FromMaybe(0)
, Nan::To<double>(info[2]).FromMaybe(0)
, Nan::To<double>(info[3]).FromMaybe(0)
, Nan::To<double>(info[4]).FromMaybe(0)
, Nan::To<double>(info[5]).FromMaybe(0));
grad->Wrap(info.This());
info.GetReturnValue().Set(info.This());
return;
}
return Nan::ThrowTypeError("invalid arguments");
}
/*
* Add color stop.
*/
NAN_METHOD(Gradient::AddColorStop) {
if (!info[0]->IsNumber())
return Nan::ThrowTypeError("offset required");
if (!info[1]->IsString())
return Nan::ThrowTypeError("color string required");
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(info.This());
short ok;
Nan::Utf8String str(info[1]);
uint32_t rgba = rgba_from_string(*str, &ok);
if (ok) {
rgba_t color = rgba_create(rgba);
cairo_pattern_add_color_stop_rgba(
grad->pattern()
, Nan::To<double>(info[0]).FromMaybe(0)
, color.r
, color.g
, color.b
, color.a);
} else {
return Nan::ThrowTypeError("parse color failed");
}
}
/*
* Initialize linear gradient.
*/
Gradient::Gradient(double x0, double y0, double x1, double y1) {
_pattern = cairo_pattern_create_linear(x0, y0, x1, y1);
}
/*
* Initialize radial gradient.
*/
Gradient::Gradient(double x0, double y0, double r0, double x1, double y1, double r1) {
_pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
}
/*
* Destroy the pattern.
*/
Gradient::~Gradient() {
cairo_pattern_destroy(_pattern);
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <nan.h>
#include <v8.h>
#include <cairo.h>
class Gradient: public Nan::ObjectWrap {
public:
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(AddColorStop);
Gradient(double x0, double y0, double x1, double y1);
Gradient(double x0, double y0, double r0, double x1, double y1, double r1);
inline cairo_pattern_t *pattern(){ return _pattern; }
private:
~Gradient();
cairo_pattern_t *_pattern;
};

View File

@@ -0,0 +1,136 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "CanvasPattern.h"
#include "Canvas.h"
#include "Image.h"
using namespace v8;
const cairo_user_data_key_t *pattern_repeat_key;
Nan::Persistent<FunctionTemplate> Pattern::constructor;
Nan::Persistent<Function> Pattern::_DOMMatrix;
/*
* Initialize CanvasPattern.
*/
void
Pattern::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Pattern::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
Nan::SetPrototypeMethod(ctor, "setTransform", SetTransform);
// Prototype
Local<Context> ctx = Nan::GetCurrentContext();
Nan::Set(target, Nan::New("CanvasPattern").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
Nan::Set(target, Nan::New("CanvasPatternInit").ToLocalChecked(), Nan::New<Function>(SaveExternalModules));
}
/*
* Save some external modules as private references.
*/
NAN_METHOD(Pattern::SaveExternalModules) {
_DOMMatrix.Reset(Nan::To<Function>(info[0]).ToLocalChecked());
}
/*
* Initialize a new CanvasPattern.
*/
NAN_METHOD(Pattern::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
}
cairo_surface_t *surface;
Local<Object> obj = Nan::To<Object>(info[0]).ToLocalChecked();
// Image
if (Nan::New(Image::constructor)->HasInstance(obj)) {
Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
if (!img->isComplete()) {
return Nan::ThrowError("Image given has not completed loading");
}
surface = img->surface();
// Canvas
} else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
surface = canvas->surface();
// Invalid
} else {
return Nan::ThrowTypeError("Image or Canvas expected");
}
repeat_type_t repeat = REPEAT;
if (0 == strcmp("no-repeat", *Nan::Utf8String(info[1]))) {
repeat = NO_REPEAT;
} else if (0 == strcmp("repeat-x", *Nan::Utf8String(info[1]))) {
repeat = REPEAT_X;
} else if (0 == strcmp("repeat-y", *Nan::Utf8String(info[1]))) {
repeat = REPEAT_Y;
}
Pattern *pattern = new Pattern(surface, repeat);
pattern->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
/*
* Set the pattern-space to user-space transform.
*/
NAN_METHOD(Pattern::SetTransform) {
Pattern *pattern = Nan::ObjectWrap::Unwrap<Pattern>(info.This());
Local<Context> ctx = Nan::GetCurrentContext();
Local<Object> mat = Nan::To<Object>(info[0]).ToLocalChecked();
#if NODE_MAJOR_VERSION >= 8
if (!mat->InstanceOf(ctx, _DOMMatrix.Get(Isolate::GetCurrent())).ToChecked()) {
return Nan::ThrowTypeError("Expected DOMMatrix");
}
#endif
cairo_matrix_t matrix;
cairo_matrix_init(&matrix,
Nan::To<double>(Nan::Get(mat, Nan::New("a").ToLocalChecked()).ToLocalChecked()).FromMaybe(1),
Nan::To<double>(Nan::Get(mat, Nan::New("b").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
Nan::To<double>(Nan::Get(mat, Nan::New("c").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
Nan::To<double>(Nan::Get(mat, Nan::New("d").ToLocalChecked()).ToLocalChecked()).FromMaybe(1),
Nan::To<double>(Nan::Get(mat, Nan::New("e").ToLocalChecked()).ToLocalChecked()).FromMaybe(0),
Nan::To<double>(Nan::Get(mat, Nan::New("f").ToLocalChecked()).ToLocalChecked()).FromMaybe(0)
);
cairo_matrix_invert(&matrix);
cairo_pattern_set_matrix(pattern->_pattern, &matrix);
}
/*
* Initialize pattern.
*/
Pattern::Pattern(cairo_surface_t *surface, repeat_type_t repeat) {
_pattern = cairo_pattern_create_for_surface(surface);
_repeat = repeat;
cairo_pattern_set_user_data(_pattern, pattern_repeat_key, &_repeat, NULL);
}
repeat_type_t Pattern::get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern) {
void *ud = cairo_pattern_get_user_data(pattern, pattern_repeat_key);
return *reinterpret_cast<repeat_type_t*>(ud);
}
/*
* Destroy the pattern.
*/
Pattern::~Pattern() {
cairo_pattern_destroy(_pattern);
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2011 LearnBoost <tj@learnboost.com>
#pragma once
#include <cairo.h>
#include <nan.h>
#include <v8.h>
/*
* Canvas types.
*/
typedef enum {
NO_REPEAT, // match CAIRO_EXTEND_NONE
REPEAT, // match CAIRO_EXTEND_REPEAT
REPEAT_X, // needs custom processing
REPEAT_Y // needs custom processing
} repeat_type_t;
extern const cairo_user_data_key_t *pattern_repeat_key;
class Pattern: public Nan::ObjectWrap {
public:
static Nan::Persistent<v8::FunctionTemplate> constructor;
static Nan::Persistent<v8::Function> _DOMMatrix;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(SaveExternalModules);
static NAN_METHOD(SetTransform);
static repeat_type_t get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern);
Pattern(cairo_surface_t *surface, repeat_type_t repeat);
inline cairo_pattern_t *pattern(){ return _pattern; }
private:
~Pattern();
cairo_pattern_t *_pattern;
repeat_type_t _repeat;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "cairo.h"
#include "Canvas.h"
#include "color.h"
#include "nan.h"
#include <pango/pangocairo.h>
#include <stack>
/*
* State struct.
*
* Used in conjunction with Save() / Restore() since
* cairo's gstate maintains only a single source pattern at a time.
*/
struct canvas_state_t {
rgba_t fill = { 0, 0, 0, 1 };
rgba_t stroke = { 0, 0, 0, 1 };
rgba_t shadow = { 0, 0, 0, 0 };
double shadowOffsetX = 0.;
double shadowOffsetY = 0.;
cairo_pattern_t* fillPattern = nullptr;
cairo_pattern_t* strokePattern = nullptr;
cairo_pattern_t* fillGradient = nullptr;
cairo_pattern_t* strokeGradient = nullptr;
PangoFontDescription* fontDescription = nullptr;
std::string font = "10px sans-serif";
cairo_filter_t patternQuality = CAIRO_FILTER_GOOD;
float globalAlpha = 1.f;
int shadowBlur = 0;
text_align_t textAlignment = TEXT_ALIGNMENT_LEFT; // TODO default is supposed to be START
text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
bool imageSmoothingEnabled = true;
canvas_state_t() {
fontDescription = pango_font_description_from_string("sans");
pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE);
}
canvas_state_t(const canvas_state_t& other) {
fill = other.fill;
stroke = other.stroke;
patternQuality = other.patternQuality;
fillPattern = other.fillPattern;
strokePattern = other.strokePattern;
fillGradient = other.fillGradient;
strokeGradient = other.strokeGradient;
globalAlpha = other.globalAlpha;
textAlignment = other.textAlignment;
textBaseline = other.textBaseline;
shadow = other.shadow;
shadowBlur = other.shadowBlur;
shadowOffsetX = other.shadowOffsetX;
shadowOffsetY = other.shadowOffsetY;
textDrawingMode = other.textDrawingMode;
fontDescription = pango_font_description_copy(other.fontDescription);
font = other.font;
imageSmoothingEnabled = other.imageSmoothingEnabled;
}
~canvas_state_t() {
pango_font_description_free(fontDescription);
}
};
/*
* Equivalent to a PangoRectangle but holds floats instead of ints
* (software pixels are stored here instead of pango units)
*
* Should be compatible with PANGO_ASCENT, PANGO_LBEARING, etc.
*/
typedef struct {
float x;
float y;
float width;
float height;
} float_rectangle;
class Context2d : public Nan::ObjectWrap {
public:
std::stack<canvas_state_t> states;
canvas_state_t *state;
Context2d(Canvas *canvas);
static Nan::Persistent<v8::Function> _DOMMatrix;
static Nan::Persistent<v8::Function> _parseFont;
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_METHOD(SaveExternalModules);
static NAN_METHOD(DrawImage);
static NAN_METHOD(PutImageData);
static NAN_METHOD(Save);
static NAN_METHOD(Restore);
static NAN_METHOD(Rotate);
static NAN_METHOD(Translate);
static NAN_METHOD(Scale);
static NAN_METHOD(Transform);
static NAN_METHOD(GetTransform);
static NAN_METHOD(ResetTransform);
static NAN_METHOD(SetTransform);
static NAN_METHOD(IsPointInPath);
static NAN_METHOD(BeginPath);
static NAN_METHOD(ClosePath);
static NAN_METHOD(AddPage);
static NAN_METHOD(Clip);
static NAN_METHOD(Fill);
static NAN_METHOD(Stroke);
static NAN_METHOD(FillText);
static NAN_METHOD(StrokeText);
static NAN_METHOD(SetFont);
static NAN_METHOD(SetFillColor);
static NAN_METHOD(SetStrokeColor);
static NAN_METHOD(SetStrokePattern);
static NAN_METHOD(SetTextAlignment);
static NAN_METHOD(SetLineDash);
static NAN_METHOD(GetLineDash);
static NAN_METHOD(MeasureText);
static NAN_METHOD(BezierCurveTo);
static NAN_METHOD(QuadraticCurveTo);
static NAN_METHOD(LineTo);
static NAN_METHOD(MoveTo);
static NAN_METHOD(FillRect);
static NAN_METHOD(StrokeRect);
static NAN_METHOD(ClearRect);
static NAN_METHOD(Rect);
static NAN_METHOD(RoundRect);
static NAN_METHOD(Arc);
static NAN_METHOD(ArcTo);
static NAN_METHOD(Ellipse);
static NAN_METHOD(GetImageData);
static NAN_METHOD(CreateImageData);
static NAN_METHOD(GetStrokeColor);
static NAN_METHOD(CreatePattern);
static NAN_METHOD(CreateLinearGradient);
static NAN_METHOD(CreateRadialGradient);
static NAN_GETTER(GetFormat);
static NAN_GETTER(GetPatternQuality);
static NAN_GETTER(GetImageSmoothingEnabled);
static NAN_GETTER(GetGlobalCompositeOperation);
static NAN_GETTER(GetGlobalAlpha);
static NAN_GETTER(GetShadowColor);
static NAN_GETTER(GetMiterLimit);
static NAN_GETTER(GetLineCap);
static NAN_GETTER(GetLineJoin);
static NAN_GETTER(GetLineWidth);
static NAN_GETTER(GetLineDashOffset);
static NAN_GETTER(GetShadowOffsetX);
static NAN_GETTER(GetShadowOffsetY);
static NAN_GETTER(GetShadowBlur);
static NAN_GETTER(GetAntiAlias);
static NAN_GETTER(GetTextDrawingMode);
static NAN_GETTER(GetQuality);
static NAN_GETTER(GetCurrentTransform);
static NAN_GETTER(GetFillStyle);
static NAN_GETTER(GetStrokeStyle);
static NAN_GETTER(GetFont);
static NAN_GETTER(GetTextBaseline);
static NAN_GETTER(GetTextAlign);
static NAN_SETTER(SetPatternQuality);
static NAN_SETTER(SetImageSmoothingEnabled);
static NAN_SETTER(SetGlobalCompositeOperation);
static NAN_SETTER(SetGlobalAlpha);
static NAN_SETTER(SetShadowColor);
static NAN_SETTER(SetMiterLimit);
static NAN_SETTER(SetLineCap);
static NAN_SETTER(SetLineJoin);
static NAN_SETTER(SetLineWidth);
static NAN_SETTER(SetLineDashOffset);
static NAN_SETTER(SetShadowOffsetX);
static NAN_SETTER(SetShadowOffsetY);
static NAN_SETTER(SetShadowBlur);
static NAN_SETTER(SetAntiAlias);
static NAN_SETTER(SetTextDrawingMode);
static NAN_SETTER(SetQuality);
static NAN_SETTER(SetCurrentTransform);
static NAN_SETTER(SetFillStyle);
static NAN_SETTER(SetStrokeStyle);
static NAN_SETTER(SetFont);
static NAN_SETTER(SetTextBaseline);
static NAN_SETTER(SetTextAlign);
inline void setContext(cairo_t *ctx) { _context = ctx; }
inline cairo_t *context(){ return _context; }
inline Canvas *canvas(){ return _canvas; }
inline bool hasShadow();
void inline setSourceRGBA(rgba_t color);
void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
void setTextPath(double x, double y);
void blur(cairo_surface_t *surface, int radius);
void shadow(void (fn)(cairo_t *cr));
void shadowStart();
void shadowApply();
void savePath();
void restorePath();
void saveState();
void restoreState();
void inline setFillRule(v8::Local<v8::Value> value);
void fill(bool preserve = false);
void stroke(bool preserve = false);
void save();
void restore();
void setFontFromState();
void resetState();
inline PangoLayout *layout(){ return _layout; }
private:
~Context2d();
void _resetPersistentHandles();
v8::Local<v8::Value> _getFillColor();
v8::Local<v8::Value> _getStrokeColor();
void _setFillColor(v8::Local<v8::Value> arg);
void _setFillPattern(v8::Local<v8::Value> arg);
void _setStrokeColor(v8::Local<v8::Value> arg);
void _setStrokePattern(v8::Local<v8::Value> arg);
Nan::Persistent<v8::Value> _fillStyle;
Nan::Persistent<v8::Value> _strokeStyle;
Canvas *_canvas;
cairo_t *_context;
cairo_path_t *_path;
PangoLayout *_layout;
};

View File

@@ -0,0 +1,226 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "cairo.h"
#include "Canvas.h"
#include "color.h"
#include "napi.h"
#include "uv.h"
#include <pango/pangocairo.h>
#include <stack>
/*
* State struct.
*
* Used in conjunction with Save() / Restore() since
* cairo's gstate maintains only a single source pattern at a time.
*/
struct canvas_state_t {
rgba_t fill = { 0, 0, 0, 1 };
rgba_t stroke = { 0, 0, 0, 1 };
rgba_t shadow = { 0, 0, 0, 0 };
double shadowOffsetX = 0.;
double shadowOffsetY = 0.;
cairo_pattern_t* fillPattern = nullptr;
cairo_pattern_t* strokePattern = nullptr;
cairo_pattern_t* fillGradient = nullptr;
cairo_pattern_t* strokeGradient = nullptr;
PangoFontDescription* fontDescription = nullptr;
std::string font = "10px sans-serif";
cairo_filter_t patternQuality = CAIRO_FILTER_GOOD;
float globalAlpha = 1.f;
int shadowBlur = 0;
text_align_t textAlignment = TEXT_ALIGNMENT_LEFT; // TODO default is supposed to be START
text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
bool imageSmoothingEnabled = true;
canvas_state_t() {
fontDescription = pango_font_description_from_string("sans");
pango_font_description_set_absolute_size(fontDescription, 10 * PANGO_SCALE);
}
canvas_state_t(const canvas_state_t& other) {
fill = other.fill;
stroke = other.stroke;
patternQuality = other.patternQuality;
fillPattern = other.fillPattern;
strokePattern = other.strokePattern;
fillGradient = other.fillGradient;
strokeGradient = other.strokeGradient;
globalAlpha = other.globalAlpha;
textAlignment = other.textAlignment;
textBaseline = other.textBaseline;
shadow = other.shadow;
shadowBlur = other.shadowBlur;
shadowOffsetX = other.shadowOffsetX;
shadowOffsetY = other.shadowOffsetY;
textDrawingMode = other.textDrawingMode;
fontDescription = pango_font_description_copy(other.fontDescription);
font = other.font;
imageSmoothingEnabled = other.imageSmoothingEnabled;
}
~canvas_state_t() {
pango_font_description_free(fontDescription);
}
};
/*
* Equivalent to a PangoRectangle but holds floats instead of ints
* (software pixels are stored here instead of pango units)
*
* Should be compatible with PANGO_ASCENT, PANGO_LBEARING, etc.
*/
typedef struct {
float x;
float y;
float width;
float height;
} float_rectangle;
class Context2d : public Napi::ObjectWrap<Context2d> {
public:
std::stack<canvas_state_t> states;
canvas_state_t *state;
Context2d(Canvas *canvas);
static Napi::FunctionReference _DOMMatrix;
static Napi::FunctionReference _parseFont;
static Napi::FunctionReference constructor;
static void Initialize(Napi::Env& env, Napi::Object& target);
static Napi::Value New(const Napi::CallbackInfo& info);
static Napi::Value SaveExternalModules(const Napi::CallbackInfo& info);
static Napi::Value DrawImage(const Napi::CallbackInfo& info);
static Napi::Value PutImageData(const Napi::CallbackInfo& info);
static Napi::Value Save(const Napi::CallbackInfo& info);
static Napi::Value Restore(const Napi::CallbackInfo& info);
static Napi::Value Rotate(const Napi::CallbackInfo& info);
static Napi::Value Translate(const Napi::CallbackInfo& info);
static Napi::Value Scale(const Napi::CallbackInfo& info);
static Napi::Value Transform(const Napi::CallbackInfo& info);
static Napi::Value GetTransform(const Napi::CallbackInfo& info);
static Napi::Value ResetTransform(const Napi::CallbackInfo& info);
static Napi::Value SetTransform(const Napi::CallbackInfo& info);
static Napi::Value IsPointInPath(const Napi::CallbackInfo& info);
static Napi::Value BeginPath(const Napi::CallbackInfo& info);
static Napi::Value ClosePath(const Napi::CallbackInfo& info);
static Napi::Value AddPage(const Napi::CallbackInfo& info);
static Napi::Value Clip(const Napi::CallbackInfo& info);
static Napi::Value Fill(const Napi::CallbackInfo& info);
static Napi::Value Stroke(const Napi::CallbackInfo& info);
static Napi::Value FillText(const Napi::CallbackInfo& info);
static Napi::Value StrokeText(const Napi::CallbackInfo& info);
static Napi::Value SetFont(const Napi::CallbackInfo& info);
static Napi::Value SetFillColor(const Napi::CallbackInfo& info);
static Napi::Value SetStrokeColor(const Napi::CallbackInfo& info);
static Napi::Value SetStrokePattern(const Napi::CallbackInfo& info);
static Napi::Value SetTextAlignment(const Napi::CallbackInfo& info);
static Napi::Value SetLineDash(const Napi::CallbackInfo& info);
static Napi::Value GetLineDash(const Napi::CallbackInfo& info);
static Napi::Value MeasureText(const Napi::CallbackInfo& info);
static Napi::Value BezierCurveTo(const Napi::CallbackInfo& info);
static Napi::Value QuadraticCurveTo(const Napi::CallbackInfo& info);
static Napi::Value LineTo(const Napi::CallbackInfo& info);
static Napi::Value MoveTo(const Napi::CallbackInfo& info);
static Napi::Value FillRect(const Napi::CallbackInfo& info);
static Napi::Value StrokeRect(const Napi::CallbackInfo& info);
static Napi::Value ClearRect(const Napi::CallbackInfo& info);
static Napi::Value Rect(const Napi::CallbackInfo& info);
static Napi::Value RoundRect(const Napi::CallbackInfo& info);
static Napi::Value Arc(const Napi::CallbackInfo& info);
static Napi::Value ArcTo(const Napi::CallbackInfo& info);
static Napi::Value Ellipse(const Napi::CallbackInfo& info);
static Napi::Value GetImageData(const Napi::CallbackInfo& info);
static Napi::Value CreateImageData(const Napi::CallbackInfo& info);
static Napi::Value GetStrokeColor(const Napi::CallbackInfo& info);
static Napi::Value CreatePattern(const Napi::CallbackInfo& info);
static Napi::Value CreateLinearGradient(const Napi::CallbackInfo& info);
static Napi::Value CreateRadialGradient(const Napi::CallbackInfo& info);
Napi::Value GetFormat(const Napi::CallbackInfo& info);
Napi::Value GetPatternQuality(const Napi::CallbackInfo& info);
Napi::Value GetImageSmoothingEnabled(const Napi::CallbackInfo& info);
Napi::Value GetGlobalCompositeOperation(const Napi::CallbackInfo& info);
Napi::Value GetGlobalAlpha(const Napi::CallbackInfo& info);
Napi::Value GetShadowColor(const Napi::CallbackInfo& info);
Napi::Value GetMiterLimit(const Napi::CallbackInfo& info);
Napi::Value GetLineCap(const Napi::CallbackInfo& info);
Napi::Value GetLineJoin(const Napi::CallbackInfo& info);
Napi::Value GetLineWidth(const Napi::CallbackInfo& info);
Napi::Value GetLineDashOffset(const Napi::CallbackInfo& info);
Napi::Value GetShadowOffsetX(const Napi::CallbackInfo& info);
Napi::Value GetShadowOffsetY(const Napi::CallbackInfo& info);
Napi::Value GetShadowBlur(const Napi::CallbackInfo& info);
Napi::Value GetAntiAlias(const Napi::CallbackInfo& info);
Napi::Value GetTextDrawingMode(const Napi::CallbackInfo& info);
Napi::Value GetQuality(const Napi::CallbackInfo& info);
Napi::Value GetCurrentTransform(const Napi::CallbackInfo& info);
Napi::Value GetFillStyle(const Napi::CallbackInfo& info);
Napi::Value GetStrokeStyle(const Napi::CallbackInfo& info);
Napi::Value GetFont(const Napi::CallbackInfo& info);
Napi::Value GetTextBaseline(const Napi::CallbackInfo& info);
Napi::Value GetTextAlign(const Napi::CallbackInfo& info);
void SetPatternQuality(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetImageSmoothingEnabled(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetGlobalCompositeOperation(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetGlobalAlpha(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetShadowColor(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetMiterLimit(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetLineCap(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetLineJoin(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetLineWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetLineDashOffset(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetShadowOffsetX(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetShadowOffsetY(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetShadowBlur(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetAntiAlias(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetTextDrawingMode(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetQuality(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetCurrentTransform(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetFillStyle(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetStrokeStyle(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetFont(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetTextBaseline(const Napi::CallbackInfo& info, const Napi::Value& value);
void SetTextAlign(const Napi::CallbackInfo& info, const Napi::Value& value);
inline void setContext(cairo_t *ctx) { _context = ctx; }
inline cairo_t *context(){ return _context; }
inline Canvas *canvas(){ return _canvas; }
inline bool hasShadow();
void inline setSourceRGBA(rgba_t color);
void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
void setTextPath(double x, double y);
void blur(cairo_surface_t *surface, int radius);
void shadow(void (fn)(cairo_t *cr));
void shadowStart();
void shadowApply();
void savePath();
void restorePath();
void saveState();
void restoreState();
void inline setFillRule(Napi::Value value);
void fill(bool preserve = false);
void stroke(bool preserve = false);
void save();
void restore();
void setFontFromState();
void resetState();
inline PangoLayout *layout(){ return _layout; }
private:
~Context2d();
void _resetPersistentHandles();
Napi::Value _getFillColor();
Napi::Value _getStrokeColor();
void _setFillColor(Napi::Value arg);
void _setFillPattern(Napi::Value arg);
void _setStrokeColor(Napi::Value arg);
void _setStrokePattern(Napi::Value arg);
Napi::Persistent<v8::Value> _fillStyle;
Napi::Persistent<v8::Value> _strokeStyle;
Canvas *_canvas;
cairo_t *_context;
cairo_path_t *_path;
PangoLayout *_layout;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <cairo.h>
#include "CanvasError.h"
#include <functional>
#include <nan.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <v8.h>
#ifdef HAVE_JPEG
#include <jpeglib.h>
#include <jerror.h>
#endif
#ifdef HAVE_GIF
#include <gif_lib.h>
#if GIFLIB_MAJOR > 5 || GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL)
#else
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif)
#endif
#endif
#ifdef HAVE_RSVG
#include <librsvg/rsvg.h>
// librsvg <= 2.36.1, identified by undefined macro, needs an extra include
#ifndef LIBRSVG_CHECK_VERSION
#include <librsvg/rsvg-cairo.h>
#endif
#endif
using JPEGDecodeL = std::function<uint32_t (uint8_t* const src)>;
class Image: public Nan::ObjectWrap {
public:
char *filename;
int width, height;
int naturalWidth, naturalHeight;
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_GETTER(GetComplete);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
static NAN_GETTER(GetNaturalWidth);
static NAN_GETTER(GetNaturalHeight);
static NAN_GETTER(GetDataMode);
static NAN_SETTER(SetDataMode);
static NAN_SETTER(SetWidth);
static NAN_SETTER(SetHeight);
static NAN_METHOD(GetSource);
static NAN_METHOD(SetSource);
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
static int isPNG(uint8_t *data);
static int isJPEG(uint8_t *data);
static int isGIF(uint8_t *data);
static int isSVG(uint8_t *data, unsigned len);
static int isBMP(uint8_t *data, unsigned len);
static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len);
inline int isComplete(){ return COMPLETE == state; }
cairo_surface_t *surface();
cairo_status_t loadSurface();
cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadPNGFromBuffer(uint8_t *buf);
cairo_status_t loadPNG();
void clearData();
#ifdef HAVE_RSVG
cairo_status_t loadSVGFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadSVG(FILE *stream);
cairo_status_t renderSVGToSurface();
#endif
#ifdef HAVE_GIF
cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadGIF(FILE *stream);
#endif
#ifdef HAVE_JPEG
cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadJPEG(FILE *stream);
void jpegToARGB(jpeg_decompress_struct* args, uint8_t* data, uint8_t* src, JPEGDecodeL decode);
cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info);
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
#endif
cairo_status_t loadBMPFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadBMP(FILE *stream);
CanvasError errorInfo;
void loaded();
cairo_status_t load();
Image();
enum {
DEFAULT
, LOADING
, COMPLETE
} state;
enum data_mode_t {
DATA_IMAGE = 1
, DATA_MIME = 2
} data_mode;
typedef enum {
UNKNOWN
, GIF
, JPEG
, PNG
, SVG
} type;
static type extension(const char *filename);
private:
cairo_surface_t *_surface;
uint8_t *_data = nullptr;
int _data_len;
#ifdef HAVE_RSVG
RsvgHandle *_rsvg;
bool _is_svg;
int _svg_last_width;
int _svg_last_height;
#endif
~Image();
};

View File

@@ -0,0 +1,146 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "ImageData.h"
using namespace v8;
Nan::Persistent<FunctionTemplate> ImageData::constructor;
/*
* Initialize ImageData.
*/
void
ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::HandleScope scope;
// Constructor
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageData::New);
constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New("ImageData").ToLocalChecked());
// Prototype
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
Local<Context> ctx = Nan::GetCurrentContext();
Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction(ctx).ToLocalChecked());
}
/*
* Initialize a new ImageData object.
*/
NAN_METHOD(ImageData::New) {
if (!info.IsConstructCall()) {
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
}
Local<TypedArray> dataArray;
uint32_t width;
uint32_t height;
int length;
if (info[0]->IsUint32() && info[1]->IsUint32()) {
width = Nan::To<uint32_t>(info[0]).FromMaybe(0);
if (width == 0) {
Nan::ThrowRangeError("The source width is zero.");
return;
}
height = Nan::To<uint32_t>(info[1]).FromMaybe(0);
if (height == 0) {
Nan::ThrowRangeError("The source height is zero.");
return;
}
length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented.
dataArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
} else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) {
dataArray = info[0].As<Uint8ClampedArray>();
length = dataArray->Length();
if (length == 0) {
Nan::ThrowRangeError("The input data has a zero byte length.");
return;
}
// Don't assert that the ImageData length is a multiple of four because some
// data formats are not 4 BPP.
width = Nan::To<uint32_t>(info[1]).FromMaybe(0);
if (width == 0) {
Nan::ThrowRangeError("The source width is zero.");
return;
}
// Don't assert that the byte length is a multiple of 4 * width, ditto.
if (info[2]->IsUint32()) { // Explicit height given
height = Nan::To<uint32_t>(info[2]).FromMaybe(0);
} else { // Calculate height assuming 4 BPP
int size = length / 4;
height = size / width;
}
} else if (info[0]->IsUint16Array() && info[1]->IsUint32()) { // Intended for RGB16_565 format
dataArray = info[0].As<Uint16Array>();
length = dataArray->Length();
if (length == 0) {
Nan::ThrowRangeError("The input data has a zero byte length.");
return;
}
width = Nan::To<uint32_t>(info[1]).FromMaybe(0);
if (width == 0) {
Nan::ThrowRangeError("The source width is zero.");
return;
}
if (info[2]->IsUint32()) { // Explicit height given
height = Nan::To<uint32_t>(info[2]).FromMaybe(0);
} else { // Calculate height assuming 2 BPP
int size = length / 2;
height = size / width;
}
} else {
Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)");
return;
}
Nan::TypedArrayContents<uint8_t> dataPtr(dataArray);
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(*dataPtr), width, height);
imageData->Wrap(info.This());
Nan::Set(info.This(), Nan::New("data").ToLocalChecked(), dataArray).Check();
info.GetReturnValue().Set(info.This());
}
/*
* Get width.
*/
NAN_GETTER(ImageData::GetWidth) {
if (!ImageData::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
Nan::ThrowTypeError("Method ImageData.GetWidth called on incompatible receiver");
return;
}
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(imageData->width()));
}
/*
* Get height.
*/
NAN_GETTER(ImageData::GetHeight) {
if (!ImageData::constructor.Get(info.GetIsolate())->HasInstance(info.This())) {
Nan::ThrowTypeError("Method ImageData.GetHeight called on incompatible receiver");
return;
}
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(imageData->height()));
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <nan.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <v8.h>
class ImageData: public Nan::ObjectWrap {
public:
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
static NAN_METHOD(New);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
inline int width() { return _width; }
inline int height() { return _height; }
inline uint8_t *data() { return _data; }
ImageData(uint8_t *data, int width, int height) : _width(width), _height(height), _data(data) {}
private:
int _width;
int _height;
uint8_t *_data;
};

View File

@@ -0,0 +1,8 @@
#include <napi.h>
struct InstanceData {
Napi::FunctionReference ImageBackendCtor;
Napi::FunctionReference PdfBackendCtor;
Napi::FunctionReference SvgBackendCtor;
Napi::FunctionReference CanvasCtor;
};

View File

@@ -0,0 +1,167 @@
#pragma once
#include "closure.h"
#include <jpeglib.h>
#include <jerror.h>
/*
* Expanded data destination object for closure output,
* inspired by IJG's jdatadst.c
*/
struct closure_destination_mgr {
jpeg_destination_mgr pub;
JpegClosure* closure;
JOCTET *buffer;
int bufsize;
};
void
init_closure_destination(j_compress_ptr cinfo){
// we really don't have to do anything here
}
boolean
empty_closure_output_buffer(j_compress_ptr cinfo){
Nan::HandleScope scope;
Nan::AsyncResource async("canvas:empty_closure_output_buffer");
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
v8::Local<v8::Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
// emit "data"
v8::Local<v8::Value> argv[2] = {
Nan::Null()
, buf
};
dest->closure->cb.Call(sizeof argv / sizeof *argv, argv, &async);
dest->buffer = (JOCTET *)malloc(dest->bufsize);
cinfo->dest->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize;
return true;
}
void
term_closure_destination(j_compress_ptr cinfo){
Nan::HandleScope scope;
Nan::AsyncResource async("canvas:term_closure_destination");
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
/* emit remaining data */
v8::Local<v8::Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer).ToLocalChecked();
v8::Local<v8::Value> data_argv[2] = {
Nan::Null()
, buf
};
dest->closure->cb.Call(sizeof data_argv / sizeof *data_argv, data_argv, &async);
// emit "end"
v8::Local<v8::Value> end_argv[2] = {
Nan::Null()
, Nan::Null()
};
dest->closure->cb.Call(sizeof end_argv / sizeof *end_argv, end_argv, &async);
}
void
jpeg_closure_dest(j_compress_ptr cinfo, JpegClosure* closure, int bufsize){
closure_destination_mgr * dest;
/* The destination object is made permanent so that multiple JPEG images
* can be written to the same buffer without re-executing jpeg_mem_dest.
*/
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
cinfo->dest = (struct jpeg_destination_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(closure_destination_mgr));
}
dest = (closure_destination_mgr *) cinfo->dest;
cinfo->dest->init_destination = &init_closure_destination;
cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
cinfo->dest->term_destination = &term_closure_destination;
dest->closure = closure;
dest->bufsize = bufsize;
dest->buffer = (JOCTET *)malloc(bufsize);
cinfo->dest->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize;
}
void encode_jpeg(jpeg_compress_struct cinfo, cairo_surface_t *surface, int quality, bool progressive, int chromaHSampFactor, int chromaVSampFactor) {
int w = cairo_image_surface_get_width(surface);
int h = cairo_image_surface_get_height(surface);
cinfo.in_color_space = JCS_RGB;
cinfo.input_components = 3;
cinfo.image_width = w;
cinfo.image_height = h;
jpeg_set_defaults(&cinfo);
if (progressive)
jpeg_simple_progression(&cinfo);
jpeg_set_quality(&cinfo, quality, (quality < 25) ? 0 : 1);
cinfo.comp_info[0].h_samp_factor = chromaHSampFactor;
cinfo.comp_info[0].v_samp_factor = chromaVSampFactor;
JSAMPROW slr;
jpeg_start_compress(&cinfo, TRUE);
unsigned char *dst;
unsigned int *src = (unsigned int *)cairo_image_surface_get_data(surface);
int sl = 0;
dst = (unsigned char *)malloc(w * 3);
while (sl < h) {
unsigned char *dp = dst;
int x = 0;
while (x < w) {
dp[0] = (*src >> 16) & 255;
dp[1] = (*src >> 8) & 255;
dp[2] = *src & 255;
src++;
dp += 3;
x++;
}
slr = dst;
jpeg_write_scanlines(&cinfo, &slr, 1);
sl++;
}
free(dst);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
}
void
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, JpegClosure* closure) {
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_closure_dest(&cinfo, closure, bufsize);
encode_jpeg(
cinfo,
surface,
closure->quality,
closure->progressive,
closure->chromaSubsampling,
closure->chromaSubsampling);
}
void
write_to_jpeg_buffer(cairo_surface_t* surface, JpegClosure* closure) {
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
cinfo.client_data = closure;
cinfo.dest = closure->jpeg_dest_mgr;
encode_jpeg(
cinfo,
surface,
closure->quality,
closure->progressive,
closure->chromaSubsampling,
closure->chromaSubsampling);
}

View File

@@ -0,0 +1,292 @@
#pragma once
#include <cairo.h>
#include "closure.h"
#include <cmath> // round
#include <cstdlib>
#include <cstring>
#include <png.h>
#include <pngconf.h>
#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
#define likely(expr) (__builtin_expect (!!(expr), 1))
#define unlikely(expr) (__builtin_expect (!!(expr), 0))
#else
#define likely(expr) (expr)
#define unlikely(expr) (expr)
#endif
static void canvas_png_flush(png_structp png_ptr) {
/* Do nothing; fflush() is said to be just a waste of energy. */
(void) png_ptr; /* Stifle compiler warning */
}
/* Converts native endian xRGB => RGBx bytes */
static void canvas_convert_data_to_bytes(png_structp png, png_row_infop row_info, png_bytep data) {
unsigned int i;
for (i = 0; i < row_info->rowbytes; i += 4) {
uint8_t *b = &data[i];
uint32_t pixel;
memcpy(&pixel, b, sizeof (uint32_t));
b[0] = (pixel & 0xff0000) >> 16;
b[1] = (pixel & 0x00ff00) >> 8;
b[2] = (pixel & 0x0000ff) >> 0;
b[3] = 0;
}
}
/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
static void canvas_unpremultiply_data(png_structp png, png_row_infop row_info, png_bytep data) {
unsigned int i;
for (i = 0; i < row_info->rowbytes; i += 4) {
uint8_t *b = &data[i];
uint32_t pixel;
uint8_t alpha;
memcpy(&pixel, b, sizeof (uint32_t));
alpha = (pixel & 0xff000000) >> 24;
if (alpha == 0) {
b[0] = b[1] = b[2] = b[3] = 0;
} else {
b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
b[1] = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
b[2] = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
b[3] = alpha;
}
}
}
/* Converts RGB16_565 format data to RGBA32 */
static void canvas_convert_565_to_888(png_structp png, png_row_infop row_info, png_bytep data) {
// Loop in reverse to unpack in-place.
for (ptrdiff_t col = row_info->width - 1; col >= 0; col--) {
uint8_t* src = &data[col * sizeof(uint16_t)];
uint8_t* dst = &data[col * 3];
uint16_t pixel;
memcpy(&pixel, src, sizeof(uint16_t));
// Convert and rescale to the full 0-255 range
// See http://stackoverflow.com/a/29326693
const uint8_t red5 = (pixel & 0xF800) >> 11;
const uint8_t green6 = (pixel & 0x7E0) >> 5;
const uint8_t blue5 = (pixel & 0x001F);
dst[0] = ((red5 * 255 + 15) / 31);
dst[1] = ((green6 * 255 + 31) / 63);
dst[2] = ((blue5 * 255 + 15) / 31);
}
}
struct canvas_png_write_closure_t {
cairo_write_func_t write_func;
PngClosure* closure;
};
#ifdef PNG_SETJMP_SUPPORTED
bool setjmp_wrapper(png_structp png) {
return setjmp(png_jmpbuf(png));
}
#endif
static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, canvas_png_write_closure_t *closure) {
unsigned int i;
cairo_status_t status = CAIRO_STATUS_SUCCESS;
uint8_t *data;
png_structp png;
png_infop info;
png_bytep *volatile rows = NULL;
png_color_16 white;
int png_color_type;
int bpc;
unsigned int width = cairo_image_surface_get_width(surface);
unsigned int height = cairo_image_surface_get_height(surface);
data = cairo_image_surface_get_data(surface);
if (data == NULL) {
status = CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
return status;
}
cairo_surface_flush(surface);
if (width == 0 || height == 0) {
status = CAIRO_STATUS_WRITE_ERROR;
return status;
}
rows = (png_bytep *) malloc(height * sizeof (png_byte*));
if (unlikely(rows == NULL)) {
status = CAIRO_STATUS_NO_MEMORY;
return status;
}
int stride = cairo_image_surface_get_stride(surface);
for (i = 0; i < height; i++) {
rows[i] = (png_byte *) data + i * stride;
}
#ifdef PNG_USER_MEM_SUPPORTED
png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL);
#else
png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
#endif
if (unlikely(png == NULL)) {
status = CAIRO_STATUS_NO_MEMORY;
free(rows);
return status;
}
info = png_create_info_struct (png);
if (unlikely(info == NULL)) {
status = CAIRO_STATUS_NO_MEMORY;
png_destroy_write_struct(&png, &info);
free(rows);
return status;
}
#ifdef PNG_SETJMP_SUPPORTED
if (setjmp_wrapper(png)) {
png_destroy_write_struct(&png, &info);
free(rows);
return status;
}
#endif
png_set_write_fn(png, closure, write_func, canvas_png_flush);
png_set_compression_level(png, closure->closure->compressionLevel);
png_set_filter(png, 0, closure->closure->filters);
if (closure->closure->resolution != 0) {
uint32_t res = static_cast<uint32_t>(round(static_cast<double>(closure->closure->resolution) * 39.3701));
png_set_pHYs(png, info, res, res, PNG_RESOLUTION_METER);
}
cairo_format_t format = cairo_image_surface_get_format(surface);
switch (format) {
case CAIRO_FORMAT_ARGB32:
bpc = 8;
png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
break;
#ifdef CAIRO_FORMAT_RGB30
case CAIRO_FORMAT_RGB30:
bpc = 10;
png_color_type = PNG_COLOR_TYPE_RGB;
break;
#endif
case CAIRO_FORMAT_RGB24:
bpc = 8;
png_color_type = PNG_COLOR_TYPE_RGB;
break;
case CAIRO_FORMAT_A8:
bpc = 8;
png_color_type = PNG_COLOR_TYPE_GRAY;
break;
case CAIRO_FORMAT_A1:
bpc = 1;
png_color_type = PNG_COLOR_TYPE_GRAY;
#ifndef WORDS_BIGENDIAN
png_set_packswap(png);
#endif
break;
case CAIRO_FORMAT_RGB16_565:
bpc = 8; // 565 gets upconverted to 888
png_color_type = PNG_COLOR_TYPE_RGB;
break;
case CAIRO_FORMAT_INVALID:
default:
status = CAIRO_STATUS_INVALID_FORMAT;
png_destroy_write_struct(&png, &info);
free(rows);
return status;
}
if ((format == CAIRO_FORMAT_A8 || format == CAIRO_FORMAT_A1) &&
closure->closure->palette != NULL) {
png_color_type = PNG_COLOR_TYPE_PALETTE;
}
png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
if (png_color_type == PNG_COLOR_TYPE_PALETTE) {
size_t nColors = closure->closure->nPaletteColors;
uint8_t* colors = closure->closure->palette;
uint8_t backgroundIndex = closure->closure->backgroundIndex;
png_colorp pngPalette = (png_colorp)png_malloc(png, nColors * sizeof(png_colorp));
png_bytep transparency = (png_bytep)png_malloc(png, nColors * sizeof(png_bytep));
for (i = 0; i < nColors; i++) {
pngPalette[i].red = colors[4 * i];
pngPalette[i].green = colors[4 * i + 1];
pngPalette[i].blue = colors[4 * i + 2];
transparency[i] = colors[4 * i + 3];
}
png_set_PLTE(png, info, pngPalette, nColors);
png_set_tRNS(png, info, transparency, nColors, NULL);
png_set_packing(png); // pack pixels
// have libpng free palette and trans:
png_data_freer(png, info, PNG_DESTROY_WILL_FREE_DATA, PNG_FREE_PLTE | PNG_FREE_TRNS);
png_color_16 bkg;
bkg.index = backgroundIndex;
png_set_bKGD(png, info, &bkg);
}
if (png_color_type != PNG_COLOR_TYPE_PALETTE) {
white.gray = (1 << bpc) - 1;
white.red = white.blue = white.green = white.gray;
png_set_bKGD(png, info, &white);
}
/* We have to call png_write_info() before setting up the write
* transformation, since it stores data internally in 'png'
* that is needed for the write transformation functions to work.
*/
png_write_info(png, info);
if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
png_set_write_user_transform_fn(png, canvas_unpremultiply_data);
} else if (format == CAIRO_FORMAT_RGB16_565) {
png_set_write_user_transform_fn(png, canvas_convert_565_to_888);
} else if (png_color_type == PNG_COLOR_TYPE_RGB) {
png_set_write_user_transform_fn(png, canvas_convert_data_to_bytes);
png_set_filler(png, 0, PNG_FILLER_AFTER);
}
png_write_image(png, rows);
png_write_end(png, info);
png_destroy_write_struct(&png, &info);
free(rows);
return status;
}
static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t size) {
cairo_status_t status;
struct canvas_png_write_closure_t *png_closure;
png_closure = (struct canvas_png_write_closure_t *) png_get_io_ptr(png);
status = png_closure->write_func(png_closure->closure, data, size);
if (unlikely(status)) {
cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png);
if (*error == CAIRO_STATUS_SUCCESS) {
*error = status;
}
png_error(png, NULL);
}
}
static cairo_status_t canvas_write_to_png_stream(cairo_surface_t *surface, cairo_write_func_t write_func, PngClosure* closure) {
struct canvas_png_write_closure_t png_closure;
if (cairo_surface_status(surface)) {
return cairo_surface_status(surface);
}
png_closure.write_func = write_func;
png_closure.closure = closure;
return canvas_write_png(surface, canvas_stream_write_func, &png_closure);
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
template <typename T>
class Point {
public:
T x, y;
Point(T x=0, T y=0): x(x), y(y) {}
Point(const Point&) = default;
Point& operator=(const Point&) = default;
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include <cctype>
inline bool streq_casein(std::string& str1, std::string& str2) {
return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), [](char& c1, char& c2) {
return c1 == c2 || std::toupper(c1) == std::toupper(c2);
});
}

View File

@@ -0,0 +1,112 @@
#include "Backend.h"
#include <string>
Backend::Backend(std::string name, int width, int height)
: name(name)
, width(width)
, height(height)
{}
Backend::~Backend()
{
this->destroySurface();
}
void Backend::init(const Nan::FunctionCallbackInfo<v8::Value> &info) {
int width = 0;
int height = 0;
if (info[0]->IsNumber()) width = Nan::To<uint32_t>(info[0]).FromMaybe(0);
if (info[1]->IsNumber()) height = Nan::To<uint32_t>(info[1]).FromMaybe(0);
Backend *backend = construct(width, height);
backend->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
void Backend::setCanvas(Canvas* _canvas)
{
this->canvas = _canvas;
}
cairo_surface_t* Backend::recreateSurface()
{
this->destroySurface();
return this->createSurface();
}
DLL_PUBLIC cairo_surface_t* Backend::getSurface() {
if (!surface) createSurface();
return surface;
}
void Backend::destroySurface()
{
if(this->surface)
{
cairo_surface_destroy(this->surface);
this->surface = NULL;
}
}
std::string Backend::getName()
{
return name;
}
int Backend::getWidth()
{
return this->width;
}
void Backend::setWidth(int width_)
{
this->width = width_;
this->recreateSurface();
}
int Backend::getHeight()
{
return this->height;
}
void Backend::setHeight(int height_)
{
this->height = height_;
this->recreateSurface();
}
bool Backend::isSurfaceValid(){
bool hadSurface = surface != NULL;
bool isValid = true;
cairo_status_t status = cairo_surface_status(getSurface());
if (status != CAIRO_STATUS_SUCCESS) {
error = cairo_status_to_string(status);
isValid = false;
}
if (!hadSurface)
destroySurface();
return isValid;
}
BackendOperationNotAvailable::BackendOperationNotAvailable(Backend* backend,
std::string operation_name)
: backend(backend)
, operation_name(operation_name)
{
msg = "operation " + operation_name +
" not supported by backend " + backend->getName();
};
BackendOperationNotAvailable::~BackendOperationNotAvailable() throw() {};
const char* BackendOperationNotAvailable::what() const throw()
{
return msg.c_str();
};

View File

@@ -0,0 +1,69 @@
#pragma once
#include <cairo.h>
#include "../dll_visibility.h"
#include <exception>
#include <nan.h>
#include <string>
#include <v8.h>
class Canvas;
class Backend : public Nan::ObjectWrap
{
private:
const std::string name;
const char* error = NULL;
protected:
int width;
int height;
cairo_surface_t* surface = nullptr;
Canvas* canvas = nullptr;
Backend(std::string name, int width, int height);
static void init(const Nan::FunctionCallbackInfo<v8::Value> &info);
static Backend *construct(int width, int height){ return nullptr; }
public:
virtual ~Backend();
void setCanvas(Canvas* canvas);
virtual cairo_surface_t* createSurface() = 0;
virtual cairo_surface_t* recreateSurface();
DLL_PUBLIC cairo_surface_t* getSurface();
virtual void destroySurface();
DLL_PUBLIC std::string getName();
DLL_PUBLIC int getWidth();
virtual void setWidth(int width);
DLL_PUBLIC int getHeight();
virtual void setHeight(int height);
// Overridden by ImageBackend. SVG and PDF thus always return INVALID.
virtual cairo_format_t getFormat() {
return CAIRO_FORMAT_INVALID;
}
bool isSurfaceValid();
inline const char* getError(){ return error; }
};
class BackendOperationNotAvailable: public std::exception
{
private:
Backend* backend;
std::string operation_name;
std::string msg;
public:
BackendOperationNotAvailable(Backend* backend, std::string operation_name);
~BackendOperationNotAvailable() throw();
const char* what() const throw();
};

View File

@@ -0,0 +1,74 @@
#include "ImageBackend.h"
using namespace v8;
ImageBackend::ImageBackend(int width, int height)
: Backend("image", width, height)
{}
Backend *ImageBackend::construct(int width, int height){
return new ImageBackend(width, height);
}
// This returns an approximate value only, suitable for Nan::AdjustExternalMemory.
// The formats that don't map to intrinsic types (RGB30, A1) round up.
int32_t ImageBackend::approxBytesPerPixel() {
switch (format) {
case CAIRO_FORMAT_ARGB32:
case CAIRO_FORMAT_RGB24:
return 4;
#ifdef CAIRO_FORMAT_RGB30
case CAIRO_FORMAT_RGB30:
return 3;
#endif
case CAIRO_FORMAT_RGB16_565:
return 2;
case CAIRO_FORMAT_A8:
case CAIRO_FORMAT_A1:
return 1;
default:
return 0;
}
}
cairo_surface_t* ImageBackend::createSurface() {
assert(!surface);
surface = cairo_image_surface_create(format, width, height);
assert(surface);
Nan::AdjustExternalMemory(approxBytesPerPixel() * width * height);
return surface;
}
void ImageBackend::destroySurface() {
if (surface) {
cairo_surface_destroy(surface);
surface = nullptr;
Nan::AdjustExternalMemory(-approxBytesPerPixel() * width * height);
}
}
cairo_format_t ImageBackend::getFormat() {
return format;
}
void ImageBackend::setFormat(cairo_format_t _format) {
this->format = _format;
}
Nan::Persistent<FunctionTemplate> ImageBackend::constructor;
void ImageBackend::Initialize(Local<Object> target) {
Nan::HandleScope scope;
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageBackend::New);
ImageBackend::constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New<String>("ImageBackend").ToLocalChecked());
Nan::Set(target,
Nan::New<String>("ImageBackend").ToLocalChecked(),
Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(ImageBackend::New) {
init(info);
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "Backend.h"
#include <v8.h>
class ImageBackend : public Backend
{
private:
cairo_surface_t* createSurface();
void destroySurface();
cairo_format_t format = DEFAULT_FORMAT;
public:
ImageBackend(int width, int height);
static Backend *construct(int width, int height);
cairo_format_t getFormat();
void setFormat(cairo_format_t format);
int32_t approxBytesPerPixel();
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(v8::Local<v8::Object> target);
static NAN_METHOD(New);
const static cairo_format_t DEFAULT_FORMAT = CAIRO_FORMAT_ARGB32;
};

View File

@@ -0,0 +1,53 @@
#include "PdfBackend.h"
#include <cairo-pdf.h>
#include "../Canvas.h"
#include "../closure.h"
using namespace v8;
PdfBackend::PdfBackend(int width, int height)
: Backend("pdf", width, height) {
createSurface();
}
PdfBackend::~PdfBackend() {
cairo_surface_finish(surface);
if (_closure) delete _closure;
destroySurface();
}
Backend *PdfBackend::construct(int width, int height){
return new PdfBackend(width, height);
}
cairo_surface_t* PdfBackend::createSurface() {
if (!_closure) _closure = new PdfSvgClosure(canvas);
surface = cairo_pdf_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height);
return surface;
}
cairo_surface_t* PdfBackend::recreateSurface() {
cairo_pdf_surface_set_size(surface, width, height);
return surface;
}
Nan::Persistent<FunctionTemplate> PdfBackend::constructor;
void PdfBackend::Initialize(Local<Object> target) {
Nan::HandleScope scope;
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(PdfBackend::New);
PdfBackend::constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New<String>("PdfBackend").ToLocalChecked());
Nan::Set(target,
Nan::New<String>("PdfBackend").ToLocalChecked(),
Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(PdfBackend::New) {
init(info);
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Backend.h"
#include "../closure.h"
#include <v8.h>
class PdfBackend : public Backend
{
private:
cairo_surface_t* createSurface();
cairo_surface_t* recreateSurface();
public:
PdfSvgClosure* _closure = NULL;
inline PdfSvgClosure* closure() { return _closure; }
PdfBackend(int width, int height);
~PdfBackend();
static Backend *construct(int width, int height);
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(v8::Local<v8::Object> target);
static NAN_METHOD(New);
};

View File

@@ -0,0 +1,61 @@
#include "SvgBackend.h"
#include <cairo-svg.h>
#include "../Canvas.h"
#include "../closure.h"
#include <cassert>
using namespace v8;
SvgBackend::SvgBackend(int width, int height)
: Backend("svg", width, height) {
createSurface();
}
SvgBackend::~SvgBackend() {
cairo_surface_finish(surface);
if (_closure) {
delete _closure;
_closure = nullptr;
}
destroySurface();
}
Backend *SvgBackend::construct(int width, int height){
return new SvgBackend(width, height);
}
cairo_surface_t* SvgBackend::createSurface() {
assert(!_closure);
_closure = new PdfSvgClosure(canvas);
surface = cairo_svg_surface_create_for_stream(PdfSvgClosure::writeVec, _closure, width, height);
return surface;
}
cairo_surface_t* SvgBackend::recreateSurface() {
cairo_surface_finish(surface);
delete _closure;
_closure = nullptr;
cairo_surface_destroy(surface);
return createSurface();
}
Nan::Persistent<FunctionTemplate> SvgBackend::constructor;
void SvgBackend::Initialize(Local<Object> target) {
Nan::HandleScope scope;
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(SvgBackend::New);
SvgBackend::constructor.Reset(ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(Nan::New<String>("SvgBackend").ToLocalChecked());
Nan::Set(target,
Nan::New<String>("SvgBackend").ToLocalChecked(),
Nan::GetFunction(ctor).ToLocalChecked()).Check();
}
NAN_METHOD(SvgBackend::New) {
init(info);
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Backend.h"
#include "../closure.h"
#include <v8.h>
class SvgBackend : public Backend
{
private:
cairo_surface_t* createSurface();
cairo_surface_t* recreateSurface();
public:
PdfSvgClosure* _closure = NULL;
inline PdfSvgClosure* closure() { return _closure; }
SvgBackend(int width, int height);
~SvgBackend();
static Backend *construct(int width, int height);
static Nan::Persistent<v8::FunctionTemplate> constructor;
static void Initialize(v8::Local<v8::Object> target);
static NAN_METHOD(New);
};

View File

@@ -0,0 +1,457 @@
#include "BMPParser.h"
#include <cassert>
using namespace std;
using namespace BMPParser;
#define MAX_IMG_SIZE 10000
#define E(cond, msg) if(cond) return setErr(msg)
#define EU(cond, msg) if(cond) return setErrUnsupported(msg)
#define EX(cond, msg) if(cond) return setErrUnknown(msg)
#define I1() get<char>()
#define U1() get<uint8_t>()
#define I2() get<int16_t>()
#define U2() get<uint16_t>()
#define I4() get<int32_t>()
#define U4() get<uint32_t>()
#define I1UC() get<char, false>()
#define U1UC() get<uint8_t, false>()
#define I2UC() get<int16_t, false>()
#define U2UC() get<uint16_t, false>()
#define I4UC() get<int32_t, false>()
#define U4UC() get<uint32_t, false>()
#define CHECK_OVERRUN(ptr, size, type) \
if((ptr) + (size) - data > len){ \
setErr("unexpected end of file"); \
return type(); \
}
Parser::~Parser(){
data = nullptr;
ptr = nullptr;
if(imgd){
delete[] imgd;
imgd = nullptr;
}
}
void Parser::parse(uint8_t *buf, int bufSize, uint8_t *format){
assert(status == Status::EMPTY);
data = ptr = buf;
len = bufSize;
// Start parsing file header
setOp("file header");
// File header signature
string fhSig = getStr(2);
string temp = "file header signature";
EU(fhSig == "BA", temp + " \"BA\"");
EU(fhSig == "CI", temp + " \"CI\"");
EU(fhSig == "CP", temp + " \"CP\"");
EU(fhSig == "IC", temp + " \"IC\"");
EU(fhSig == "PT", temp + " \"PT\"");
EX(fhSig != "BM", temp); // BM
// Length of the file should not be larger than `len`
E(U4() > static_cast<uint32_t>(len), "inconsistent file size");
// Skip unused values
skip(4);
// Offset where the pixel array (bitmap data) can be found
auto imgdOffset = U4();
// Start parsing DIB header
setOp("DIB header");
// Prepare some variables in case they are needed
uint32_t compr = 0;
uint32_t redShift = 0, greenShift = 0, blueShift = 0, alphaShift = 0;
uint32_t redMask = 0, greenMask = 0, blueMask = 0, alphaMask = 0;
double redMultp = 0, greenMultp = 0, blueMultp = 0, alphaMultp = 0;
/**
* Type of the DIB (device-independent bitmap) header
* is determined by its size. Most BMP files use BITMAPINFOHEADER.
*/
auto dibSize = U4();
temp = "DIB header";
EU(dibSize == 64, temp + " \"OS22XBITMAPHEADER\"");
EU(dibSize == 16, temp + " \"OS22XBITMAPHEADER\"");
uint32_t infoHeader = dibSize == 40 ? 1 :
dibSize == 52 ? 2 :
dibSize == 56 ? 3 :
dibSize == 108 ? 4 :
dibSize == 124 ? 5 : 0;
// BITMAPCOREHEADER, BITMAP*INFOHEADER, BITMAP*HEADER
auto isDibValid = dibSize == 12 || infoHeader;
EX(!isDibValid, temp);
// Image width
w = dibSize == 12 ? U2() : I4();
E(!w, "image width is 0");
E(w < 0, "negative image width");
E(w > MAX_IMG_SIZE, "too large image width");
// Image height (specification allows negative values)
h = dibSize == 12 ? U2() : I4();
E(!h, "image height is 0");
E(h > MAX_IMG_SIZE, "too large image height");
bool isHeightNegative = h < 0;
if(isHeightNegative) h = -h;
// Number of color planes (must be 1)
E(U2() != 1, "number of color planes must be 1");
// Bits per pixel (color depth)
auto bpp = U2();
auto isBppValid = bpp == 1 || bpp == 4 || bpp == 8 || bpp == 16 || bpp == 24 || bpp == 32;
EU(!isBppValid, "color depth");
// Calculate image data size and padding
uint32_t expectedImgdSize = (((w * bpp + 31) >> 5) << 2) * h;
uint32_t rowPadding = (-w * bpp & 31) >> 3;
uint32_t imgdSize = 0;
// Color palette data
uint8_t* paletteStart = nullptr;
uint32_t palColNum = 0;
if(infoHeader){
// Compression type
compr = U4();
temp = "compression type";
EU(compr == 1, temp + " \"BI_RLE8\"");
EU(compr == 2, temp + " \"BI_RLE4\"");
EU(compr == 4, temp + " \"BI_JPEG\"");
EU(compr == 5, temp + " \"BI_PNG\"");
EU(compr == 6, temp + " \"BI_ALPHABITFIELDS\"");
EU(compr == 11, temp + " \"BI_CMYK\"");
EU(compr == 12, temp + " \"BI_CMYKRLE8\"");
EU(compr == 13, temp + " \"BI_CMYKRLE4\"");
// BI_RGB and BI_BITFIELDS
auto isComprValid = compr == 0 || compr == 3;
EX(!isComprValid, temp);
// Ensure that BI_BITFIELDS appears only with 16-bit or 32-bit color
E(compr == 3 && !(bpp == 16 || bpp == 32), "compression BI_BITFIELDS can be used only with 16-bit and 32-bit color depth");
// Size of the image data
imgdSize = U4();
// Horizontal and vertical resolution (ignored)
skip(8);
// Number of colors in the palette or 0 if no palette is present
palColNum = U4();
EU(palColNum && bpp > 8, "color palette and bit depth combination");
if(palColNum) paletteStart = data + dibSize + 14;
// Number of important colors used or 0 if all colors are important (generally ignored)
skip(4);
if(infoHeader >= 2){
// If BI_BITFIELDS are used, calculate masks, otherwise ignore them
if(compr == 3){
calcMaskShift(redShift, redMask, redMultp);
calcMaskShift(greenShift, greenMask, greenMultp);
calcMaskShift(blueShift, blueMask, blueMultp);
if(infoHeader >= 3) calcMaskShift(alphaShift, alphaMask, alphaMultp);
if(status == Status::ERROR) return;
}else{
skip(16);
}
// Ensure that the color space is LCS_WINDOWS_COLOR_SPACE or sRGB
if(infoHeader >= 4 && !palColNum){
string colSpace = getStr(4, 1);
EU(colSpace != "Win " && colSpace != "sRGB", "color space \"" + colSpace + "\"");
}
}
}
// Skip to the image data (there may be other chunks between, but they are optional)
E(ptr - data > imgdOffset, "image data overlaps with another structure");
ptr = data + imgdOffset;
// Start parsing image data
setOp("image data");
if(!imgdSize){
// Value 0 is allowed only for BI_RGB compression type
E(compr != 0, "missing image data size");
imgdSize = expectedImgdSize;
}else{
E(imgdSize < expectedImgdSize, "invalid image data size");
}
// Ensure that all image data is present
E(ptr - data + imgdSize > len, "not enough image data");
// Direction of reading rows
int yStart = h - 1;
int yEnd = -1;
int dy = isHeightNegative ? 1 : -1;
// In case of negative height, read rows backward
if(isHeightNegative){
yStart = 0;
yEnd = h;
}
// Allocate output image data array
int buffLen = w * h << 2;
imgd = new (nothrow) uint8_t[buffLen];
E(!imgd, "unable to allocate memory");
// Prepare color values
uint8_t color[4] = {0};
uint8_t &red = color[0];
uint8_t &green = color[1];
uint8_t &blue = color[2];
uint8_t &alpha = color[3];
// Check if pre-multiplied alpha is used
bool premul = format ? format[4] : 0;
// Main loop
for(int y = yStart; y != yEnd; y += dy){
// Use in-byte offset for bpp < 8
uint8_t colOffset = 0;
uint8_t cval = 0;
uint32_t val = 0;
for(int x = 0; x != w; x++){
// Index in the output image data
int i = (x + y * w) << 2;
switch(compr){
case 0: // BI_RGB
switch(bpp){
case 1:
if(colOffset) ptr--;
cval = (U1UC() >> (7 - colOffset)) & 1;
if(palColNum){
uint8_t* entry = paletteStart + (cval << 2);
blue = get<uint8_t>(entry);
green = get<uint8_t>(entry + 1);
red = get<uint8_t>(entry + 2);
if(status == Status::ERROR) return;
}else{
red = green = blue = cval ? 255 : 0;
}
alpha = 255;
colOffset = (colOffset + 1) & 7;
break;
case 4:
if(colOffset) ptr--;
cval = (U1UC() >> (4 - colOffset)) & 15;
if(palColNum){
uint8_t* entry = paletteStart + (cval << 2);
blue = get<uint8_t>(entry);
green = get<uint8_t>(entry + 1);
red = get<uint8_t>(entry + 2);
if(status == Status::ERROR) return;
}else{
red = green = blue = cval << 4;
}
alpha = 255;
colOffset = (colOffset + 4) & 7;
break;
case 8:
cval = U1UC();
if(palColNum){
uint8_t* entry = paletteStart + (cval << 2);
blue = get<uint8_t>(entry);
green = get<uint8_t>(entry + 1);
red = get<uint8_t>(entry + 2);
if(status == Status::ERROR) return;
}else{
red = green = blue = cval;
}
alpha = 255;
break;
case 16:
// RGB555
val = U1UC();
val |= U1UC() << 8;
red = (val >> 10) << 3;
green = (val >> 5) << 3;
blue = val << 3;
alpha = 255;
break;
case 24:
blue = U1UC();
green = U1UC();
red = U1UC();
alpha = 255;
break;
case 32:
blue = U1UC();
green = U1UC();
red = U1UC();
if(infoHeader >= 3){
alpha = U1UC();
}else{
alpha = 255;
skip(1);
}
break;
}
break;
case 3: // BI_BITFIELDS
uint32_t col = bpp == 16 ? U2UC() : U4UC();
red = ((col >> redShift) & redMask) * redMultp + .5;
green = ((col >> greenShift) & greenMask) * greenMultp + .5;
blue = ((col >> blueShift) & blueMask) * blueMultp + .5;
alpha = alphaMask ? ((col >> alphaShift) & alphaMask) * alphaMultp + .5 : 255;
break;
}
/**
* Pixel format:
* red,
* green,
* blue,
* alpha,
* is alpha pre-multiplied
* Default is [0, 1, 2, 3, 0]
*/
if(premul && alpha != 255){
double a = alpha / 255.;
red = static_cast<uint8_t>(red * a + .5);
green = static_cast<uint8_t>(green * a + .5);
blue = static_cast<uint8_t>(blue * a + .5);
}
if(format){
imgd[i] = color[format[0]];
imgd[i + 1] = color[format[1]];
imgd[i + 2] = color[format[2]];
imgd[i + 3] = color[format[3]];
}else{
imgd[i] = red;
imgd[i + 1] = green;
imgd[i + 2] = blue;
imgd[i + 3] = alpha;
}
}
// Skip unused bytes in the current row
skip(rowPadding);
}
if(status == Status::ERROR) return;
status = Status::OK;
};
void Parser::clearImgd(){ imgd = nullptr; }
int32_t Parser::getWidth() const{ return w; }
int32_t Parser::getHeight() const{ return h; }
uint8_t *Parser::getImgd() const{ return imgd; }
Status Parser::getStatus() const{ return status; }
string Parser::getErrMsg() const{
return "Error while processing " + getOp() + " - " + err;
}
template <typename T, bool check> inline T Parser::get(){
if(check)
CHECK_OVERRUN(ptr, sizeof(T), T);
T val = *(T*)ptr;
ptr += sizeof(T);
return val;
}
template <typename T, bool check> inline T Parser::get(uint8_t* pointer){
if(check)
CHECK_OVERRUN(pointer, sizeof(T), T);
T val = *(T*)pointer;
return val;
}
string Parser::getStr(int size, bool reverse){
CHECK_OVERRUN(ptr, size, string);
string val = "";
while(size--){
if(reverse) val = string(1, static_cast<char>(*ptr++)) + val;
else val += static_cast<char>(*ptr++);
}
return val;
}
inline void Parser::skip(int size){
CHECK_OVERRUN(ptr, size, void);
ptr += size;
}
void Parser::calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp){
mask = U4();
shift = 0;
if(mask == 0) return;
while(~mask & 1){
mask >>= 1;
shift++;
}
E(mask & (mask + 1), "invalid color mask");
multp = 255. / mask;
}
void Parser::setOp(string val){
if(status != Status::EMPTY) return;
op = val;
}
string Parser::getOp() const{
return op;
}
void Parser::setErrUnsupported(string msg){
setErr("unsupported " + msg);
}
void Parser::setErrUnknown(string msg){
setErr("unknown " + msg);
}
void Parser::setErr(string msg){
if(status != Status::EMPTY) return;
err = msg;
status = Status::ERROR;
}
string Parser::getErr() const{
return err;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#ifdef ERROR
#define ERROR_ ERROR
#undef ERROR
#endif
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <string>
namespace BMPParser{
enum Status{
EMPTY,
OK,
ERROR,
};
class Parser{
public:
Parser()=default;
~Parser();
void parse(uint8_t *buf, int bufSize, uint8_t *format=nullptr);
void clearImgd();
int32_t getWidth() const;
int32_t getHeight() const;
uint8_t *getImgd() const;
Status getStatus() const;
std::string getErrMsg() const;
private:
Status status = Status::EMPTY;
uint8_t *data = nullptr;
uint8_t *ptr = nullptr;
int len = 0;
int32_t w = 0;
int32_t h = 0;
uint8_t *imgd = nullptr;
std::string err = "";
std::string op = "";
template <typename T, bool check=true> inline T get();
template <typename T, bool check=true> inline T get(uint8_t* pointer);
std::string getStr(int len, bool reverse=false);
inline void skip(int len);
void calcMaskShift(uint32_t& shift, uint32_t& mask, double& multp);
void setOp(std::string val);
std::string getOp() const;
void setErrUnsupported(std::string msg);
void setErrUnknown(std::string msg);
void setErr(std::string msg);
std::string getErr() const;
};
}
#ifdef ERROR_
#define ERROR ERROR_
#undef ERROR_
#endif

View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

View File

@@ -0,0 +1,26 @@
#include "closure.h"
#ifdef HAVE_JPEG
void JpegClosure::init_destination(j_compress_ptr cinfo) {
JpegClosure* closure = (JpegClosure*)cinfo->client_data;
closure->vec.resize(PAGE_SIZE);
closure->jpeg_dest_mgr->next_output_byte = &closure->vec[0];
closure->jpeg_dest_mgr->free_in_buffer = closure->vec.size();
}
boolean JpegClosure::empty_output_buffer(j_compress_ptr cinfo) {
JpegClosure* closure = (JpegClosure*)cinfo->client_data;
size_t currentSize = closure->vec.size();
closure->vec.resize(currentSize * 1.5);
closure->jpeg_dest_mgr->next_output_byte = &closure->vec[currentSize];
closure->jpeg_dest_mgr->free_in_buffer = closure->vec.size() - currentSize;
return true;
}
void JpegClosure::term_destination(j_compress_ptr cinfo) {
JpegClosure* closure = (JpegClosure*)cinfo->client_data;
size_t finalSize = closure->vec.size() - closure->jpeg_dest_mgr->free_in_buffer;
closure->vec.resize(finalSize);
}
#endif

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include "Canvas.h"
#ifdef HAVE_JPEG
#include <jpeglib.h>
#endif
#include <nan.h>
#include <png.h>
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <vector>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
/*
* Image encoding closures.
*/
struct Closure {
std::vector<uint8_t> vec;
Nan::Callback cb;
Canvas* canvas = nullptr;
cairo_status_t status = CAIRO_STATUS_SUCCESS;
static cairo_status_t writeVec(void *c, const uint8_t *odata, unsigned len) {
Closure* closure = static_cast<Closure*>(c);
try {
closure->vec.insert(closure->vec.end(), odata, odata + len);
} catch (const std::bad_alloc &) {
return CAIRO_STATUS_NO_MEMORY;
}
return CAIRO_STATUS_SUCCESS;
}
Closure(Canvas* canvas) : canvas(canvas) {};
};
struct PdfSvgClosure : Closure {
PdfSvgClosure(Canvas* canvas) : Closure(canvas) {};
};
struct PngClosure : Closure {
uint32_t compressionLevel = 6;
uint32_t filters = PNG_ALL_FILTERS;
uint32_t resolution = 0; // 0 = unspecified
// Indexed PNGs:
uint32_t nPaletteColors = 0;
uint8_t* palette = nullptr;
uint8_t backgroundIndex = 0;
PngClosure(Canvas* canvas) : Closure(canvas) {};
};
#ifdef HAVE_JPEG
struct JpegClosure : Closure {
uint32_t quality = 75;
uint32_t chromaSubsampling = 2;
bool progressive = false;
jpeg_destination_mgr* jpeg_dest_mgr = nullptr;
static void init_destination(j_compress_ptr cinfo);
static boolean empty_output_buffer(j_compress_ptr cinfo);
static void term_destination(j_compress_ptr cinfo);
JpegClosure(Canvas* canvas) : Closure(canvas) {
jpeg_dest_mgr = new jpeg_destination_mgr;
jpeg_dest_mgr->init_destination = init_destination;
jpeg_dest_mgr->empty_output_buffer = empty_output_buffer;
jpeg_dest_mgr->term_destination = term_destination;
};
~JpegClosure() {
delete jpeg_dest_mgr;
}
};
#endif

View File

@@ -0,0 +1,779 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include "color.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <map>
#include <string>
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
/*
* Parse integer value
*/
template <typename parsed_t>
static bool
parse_integer(const char** pStr, parsed_t *pParsed) {
parsed_t& c = *pParsed;
const char*& str = *pStr;
int8_t sign=1;
c = 0;
if (*str == '-') {
sign=-1;
++str;
}
else if (*str == '+')
++str;
if (*str >= '0' && *str <= '9') {
do {
c *= 10;
c += *str++ - '0';
} while (*str >= '0' && *str <= '9');
} else {
return false;
}
if (sign<0)
c=-c;
return true;
}
/*
* Parse CSS <number> value
* Adapted from http://crackprogramming.blogspot.co.il/2012/10/implement-atof.html
*/
template <typename parsed_t>
static bool
parse_css_number(const char** pStr, parsed_t *pParsed) {
parsed_t &parsed = *pParsed;
const char*& str = *pStr;
const char* startStr = str;
if (!str || !*str)
return false;
parsed_t integerPart = 0;
parsed_t fractionPart = 0;
int divisorForFraction = 1;
int sign = 1;
int exponent = 0;
int digits = 0;
bool inFraction = false;
if (*str == '-') {
++str;
sign = -1;
}
else if (*str == '+')
++str;
while (*str != '\0') {
if (*str >= '0' && *str <= '9') {
if (digits>=std::numeric_limits<parsed_t>::digits10) {
if (!inFraction)
return false;
}
else {
++digits;
if (inFraction) {
fractionPart = fractionPart*10 + (*str - '0');
divisorForFraction *= 10;
}
else {
integerPart = integerPart*10 + (*str - '0');
}
}
}
else if (*str == '.') {
if (inFraction)
break;
else
inFraction = true;
}
else if (*str == 'e') {
++str;
if (!parse_integer(&str, &exponent))
return false;
break;
}
else
break;
++str;
}
if (str != startStr) {
parsed = sign * (integerPart + fractionPart/divisorForFraction);
for (;exponent>0;--exponent)
parsed *= 10;
for (;exponent<0;++exponent)
parsed /= 10;
return true;
}
return false;
}
/*
* Clip value to the range [minValue, maxValue]
*/
template <typename T>
static T
clip(T value, T minValue, T maxValue) {
if (value > maxValue)
value = maxValue;
if (value < minValue)
value = minValue;
return value;
}
/*
* Wrap value to the range [0, limit]
*/
template <typename T>
static T
wrap_float(T value, T limit) {
return fmod(fmod(value, limit) + limit, limit);
}
/*
* Wrap value to the range [0, limit] - currently-unused integer version of wrap_float
*/
// template <typename T>
// static T wrap_int(T value, T limit) {
// return (value % limit + limit) % limit;
// }
/*
* Parse color channel value
*/
static bool
parse_rgb_channel(const char** pStr, uint8_t *pChannel) {
int channel;
if (parse_integer(pStr, &channel)) {
*pChannel = clip(channel, 0, 255);
return true;
}
return false;
}
/*
* Parse a value in degrees
*/
static bool
parse_degrees(const char** pStr, float *pDegrees) {
float degrees;
if (parse_css_number(pStr, &degrees)) {
*pDegrees = wrap_float(degrees, 360.0f);
return true;
}
return false;
}
/*
* Parse and clip a percentage value. Returns a float in the range [0, 1].
*/
static bool
parse_clipped_percentage(const char** pStr, float *pFraction) {
float percentage;
bool result = parse_css_number(pStr,&percentage);
const char*& str = *pStr;
if (result) {
if (*str == '%') {
++str;
*pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f;
return result;
}
}
return false;
}
/*
* Macros to help with parsing inside rgba_from_*_string
*/
#define WHITESPACE \
while (' ' == *str) ++str;
#define WHITESPACE_OR_COMMA \
while (' ' == *str || ',' == *str) ++str;
#define CHANNEL(NAME) \
if (!parse_rgb_channel(&str, &NAME)) \
return 0; \
#define HUE(NAME) \
if (!parse_degrees(&str, &NAME)) \
return 0;
#define SATURATION(NAME) \
if (!parse_clipped_percentage(&str, &NAME)) \
return 0;
#define LIGHTNESS(NAME) SATURATION(NAME)
#define ALPHA(NAME) \
if (*str >= '1' && *str <= '9') { \
NAME = 1; \
} else { \
if ('0' == *str) { \
NAME = 0; \
++str; \
} \
if ('.' == *str) { \
++str; \
NAME = 0; \
float n = .1f; \
while (*str >= '0' && *str <= '9') { \
NAME += (*str++ - '0') * n; \
n *= .1f; \
} \
} \
} \
do {} while (0) // require trailing semicolon
/*
* Named colors.
*/
static const std::map<std::string, uint32_t> named_colors = {
{ "transparent", 0xFFFFFF00}
, { "aliceblue", 0xF0F8FFFF }
, { "antiquewhite", 0xFAEBD7FF }
, { "aqua", 0x00FFFFFF }
, { "aquamarine", 0x7FFFD4FF }
, { "azure", 0xF0FFFFFF }
, { "beige", 0xF5F5DCFF }
, { "bisque", 0xFFE4C4FF }
, { "black", 0x000000FF }
, { "blanchedalmond", 0xFFEBCDFF }
, { "blue", 0x0000FFFF }
, { "blueviolet", 0x8A2BE2FF }
, { "brown", 0xA52A2AFF }
, { "burlywood", 0xDEB887FF }
, { "cadetblue", 0x5F9EA0FF }
, { "chartreuse", 0x7FFF00FF }
, { "chocolate", 0xD2691EFF }
, { "coral", 0xFF7F50FF }
, { "cornflowerblue", 0x6495EDFF }
, { "cornsilk", 0xFFF8DCFF }
, { "crimson", 0xDC143CFF }
, { "cyan", 0x00FFFFFF }
, { "darkblue", 0x00008BFF }
, { "darkcyan", 0x008B8BFF }
, { "darkgoldenrod", 0xB8860BFF }
, { "darkgray", 0xA9A9A9FF }
, { "darkgreen", 0x006400FF }
, { "darkgrey", 0xA9A9A9FF }
, { "darkkhaki", 0xBDB76BFF }
, { "darkmagenta", 0x8B008BFF }
, { "darkolivegreen", 0x556B2FFF }
, { "darkorange", 0xFF8C00FF }
, { "darkorchid", 0x9932CCFF }
, { "darkred", 0x8B0000FF }
, { "darksalmon", 0xE9967AFF }
, { "darkseagreen", 0x8FBC8FFF }
, { "darkslateblue", 0x483D8BFF }
, { "darkslategray", 0x2F4F4FFF }
, { "darkslategrey", 0x2F4F4FFF }
, { "darkturquoise", 0x00CED1FF }
, { "darkviolet", 0x9400D3FF }
, { "deeppink", 0xFF1493FF }
, { "deepskyblue", 0x00BFFFFF }
, { "dimgray", 0x696969FF }
, { "dimgrey", 0x696969FF }
, { "dodgerblue", 0x1E90FFFF }
, { "firebrick", 0xB22222FF }
, { "floralwhite", 0xFFFAF0FF }
, { "forestgreen", 0x228B22FF }
, { "fuchsia", 0xFF00FFFF }
, { "gainsboro", 0xDCDCDCFF }
, { "ghostwhite", 0xF8F8FFFF }
, { "gold", 0xFFD700FF }
, { "goldenrod", 0xDAA520FF }
, { "gray", 0x808080FF }
, { "green", 0x008000FF }
, { "greenyellow", 0xADFF2FFF }
, { "grey", 0x808080FF }
, { "honeydew", 0xF0FFF0FF }
, { "hotpink", 0xFF69B4FF }
, { "indianred", 0xCD5C5CFF }
, { "indigo", 0x4B0082FF }
, { "ivory", 0xFFFFF0FF }
, { "khaki", 0xF0E68CFF }
, { "lavender", 0xE6E6FAFF }
, { "lavenderblush", 0xFFF0F5FF }
, { "lawngreen", 0x7CFC00FF }
, { "lemonchiffon", 0xFFFACDFF }
, { "lightblue", 0xADD8E6FF }
, { "lightcoral", 0xF08080FF }
, { "lightcyan", 0xE0FFFFFF }
, { "lightgoldenrodyellow", 0xFAFAD2FF }
, { "lightgray", 0xD3D3D3FF }
, { "lightgreen", 0x90EE90FF }
, { "lightgrey", 0xD3D3D3FF }
, { "lightpink", 0xFFB6C1FF }
, { "lightsalmon", 0xFFA07AFF }
, { "lightseagreen", 0x20B2AAFF }
, { "lightskyblue", 0x87CEFAFF }
, { "lightslategray", 0x778899FF }
, { "lightslategrey", 0x778899FF }
, { "lightsteelblue", 0xB0C4DEFF }
, { "lightyellow", 0xFFFFE0FF }
, { "lime", 0x00FF00FF }
, { "limegreen", 0x32CD32FF }
, { "linen", 0xFAF0E6FF }
, { "magenta", 0xFF00FFFF }
, { "maroon", 0x800000FF }
, { "mediumaquamarine", 0x66CDAAFF }
, { "mediumblue", 0x0000CDFF }
, { "mediumorchid", 0xBA55D3FF }
, { "mediumpurple", 0x9370DBFF }
, { "mediumseagreen", 0x3CB371FF }
, { "mediumslateblue", 0x7B68EEFF }
, { "mediumspringgreen", 0x00FA9AFF }
, { "mediumturquoise", 0x48D1CCFF }
, { "mediumvioletred", 0xC71585FF }
, { "midnightblue", 0x191970FF }
, { "mintcream", 0xF5FFFAFF }
, { "mistyrose", 0xFFE4E1FF }
, { "moccasin", 0xFFE4B5FF }
, { "navajowhite", 0xFFDEADFF }
, { "navy", 0x000080FF }
, { "oldlace", 0xFDF5E6FF }
, { "olive", 0x808000FF }
, { "olivedrab", 0x6B8E23FF }
, { "orange", 0xFFA500FF }
, { "orangered", 0xFF4500FF }
, { "orchid", 0xDA70D6FF }
, { "palegoldenrod", 0xEEE8AAFF }
, { "palegreen", 0x98FB98FF }
, { "paleturquoise", 0xAFEEEEFF }
, { "palevioletred", 0xDB7093FF }
, { "papayawhip", 0xFFEFD5FF }
, { "peachpuff", 0xFFDAB9FF }
, { "peru", 0xCD853FFF }
, { "pink", 0xFFC0CBFF }
, { "plum", 0xDDA0DDFF }
, { "powderblue", 0xB0E0E6FF }
, { "purple", 0x800080FF }
, { "rebeccapurple", 0x663399FF } // Source: CSS Color Level 4 draft
, { "red", 0xFF0000FF }
, { "rosybrown", 0xBC8F8FFF }
, { "royalblue", 0x4169E1FF }
, { "saddlebrown", 0x8B4513FF }
, { "salmon", 0xFA8072FF }
, { "sandybrown", 0xF4A460FF }
, { "seagreen", 0x2E8B57FF }
, { "seashell", 0xFFF5EEFF }
, { "sienna", 0xA0522DFF }
, { "silver", 0xC0C0C0FF }
, { "skyblue", 0x87CEEBFF }
, { "slateblue", 0x6A5ACDFF }
, { "slategray", 0x708090FF }
, { "slategrey", 0x708090FF }
, { "snow", 0xFFFAFAFF }
, { "springgreen", 0x00FF7FFF }
, { "steelblue", 0x4682B4FF }
, { "tan", 0xD2B48CFF }
, { "teal", 0x008080FF }
, { "thistle", 0xD8BFD8FF }
, { "tomato", 0xFF6347FF }
, { "turquoise", 0x40E0D0FF }
, { "violet", 0xEE82EEFF }
, { "wheat", 0xF5DEB3FF }
, { "white", 0xFFFFFFFF }
, { "whitesmoke", 0xF5F5F5FF }
, { "yellow", 0xFFFF00FF }
, { "yellowgreen", 0x9ACD32FF }
};
/*
* Hex digit int val.
*/
static int
h(char c) {
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return c - '0';
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
return (c - 'a') + 10;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
return (c - 'A') + 10;
}
return 0;
}
/*
* Return rgba_t from rgba.
*/
rgba_t
rgba_create(uint32_t rgba) {
rgba_t color;
color.r = (double) (rgba >> 24) / 255;
color.g = (double) (rgba >> 16 & 0xff) / 255;
color.b = (double) (rgba >> 8 & 0xff) / 255;
color.a = (double) (rgba & 0xff) / 255;
return color;
}
/*
* Return a string representation of the color.
*/
void
rgba_to_string(rgba_t rgba, char *buf, size_t len) {
if (1 == rgba.a) {
snprintf(buf, len, "#%.2x%.2x%.2x",
static_cast<int>(round(rgba.r * 255)),
static_cast<int>(round(rgba.g * 255)),
static_cast<int>(round(rgba.b * 255)));
} else {
snprintf(buf, len, "rgba(%d, %d, %d, %.2f)",
static_cast<int>(round(rgba.r * 255)),
static_cast<int>(round(rgba.g * 255)),
static_cast<int>(round(rgba.b * 255)),
rgba.a);
}
}
/*
* Return rgba from (r,g,b,a).
*/
static inline int32_t
rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return
r << 24
| g << 16
| b << 8
| a;
}
/*
* Helper function used in rgba_from_hsla().
* Based on http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
*/
static float
hue_to_rgb(float t1, float t2, float hue) {
if (hue < 0)
hue += 6;
if (hue >= 6)
hue -= 6;
if (hue < 1)
return (t2 - t1) * hue + t1;
else if (hue < 3)
return t2;
else if (hue < 4)
return (t2 - t1) * (4 - hue) + t1;
else
return t1;
}
/*
* Return rgba from (h,s,l,a).
* Expects h values in the range [0, 360), and s, l, a in the range [0, 1].
* Adapted from http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
*/
static inline int32_t
rgba_from_hsla(float h_deg, float s, float l, float a) {
uint8_t r, g, b;
float h = (6 * h_deg) / 360.0f, m1, m2;
if (l<=0.5)
m2=l*(s+1);
else
m2=l+s-l*s;
m1 = l*2 - m2;
// Scale and round the RGB components
r = (uint8_t)floor(hue_to_rgb(m1, m2, h + 2) * 255 + 0.5);
g = (uint8_t)floor(hue_to_rgb(m1, m2, h ) * 255 + 0.5);
b = (uint8_t)floor(hue_to_rgb(m1, m2, h - 2) * 255 + 0.5);
return rgba_from_rgba(r, g, b, (uint8_t) (a * 255));
}
/*
* Return rgba from (h,s,l).
* Expects h values in the range [0, 360), and s, l in the range [0, 1].
*/
static inline int32_t
rgba_from_hsl(float h_deg, float s, float l) {
return rgba_from_hsla(h_deg, s, l, 1.0);
}
/*
* Return rgba from (r,g,b).
*/
static int32_t
rgba_from_rgb(uint8_t r, uint8_t g, uint8_t b) {
return rgba_from_rgba(r, g, b, 255);
}
/*
* Return rgba from #RRGGBBAA
*/
static int32_t
rgba_from_hex8_string(const char *str) {
return rgba_from_rgba(
(h(str[0]) << 4) + h(str[1]),
(h(str[2]) << 4) + h(str[3]),
(h(str[4]) << 4) + h(str[5]),
(h(str[6]) << 4) + h(str[7])
);
}
/*
* Return rgb from "#RRGGBB".
*/
static int32_t
rgba_from_hex6_string(const char *str) {
return rgba_from_rgb(
(h(str[0]) << 4) + h(str[1])
, (h(str[2]) << 4) + h(str[3])
, (h(str[4]) << 4) + h(str[5])
);
}
/*
* Return rgba from #RGBA
*/
static int32_t
rgba_from_hex4_string(const char *str) {
return rgba_from_rgba(
(h(str[0]) << 4) + h(str[0]),
(h(str[1]) << 4) + h(str[1]),
(h(str[2]) << 4) + h(str[2]),
(h(str[3]) << 4) + h(str[3])
);
}
/*
* Return rgb from "#RGB"
*/
static int32_t
rgba_from_hex3_string(const char *str) {
return rgba_from_rgb(
(h(str[0]) << 4) + h(str[0])
, (h(str[1]) << 4) + h(str[1])
, (h(str[2]) << 4) + h(str[2])
);
}
/*
* Return rgb from "rgb()"
*/
static int32_t
rgba_from_rgb_string(const char *str, short *ok) {
if (str == strstr(str, "rgb(")) {
str += 4;
WHITESPACE;
uint8_t r = 0, g = 0, b = 0;
CHANNEL(r);
WHITESPACE_OR_COMMA;
CHANNEL(g);
WHITESPACE_OR_COMMA;
CHANNEL(b);
WHITESPACE;
return *ok = 1, rgba_from_rgb(r, g, b);
}
return *ok = 0;
}
/*
* Return rgb from "rgba()"
*/
static int32_t
rgba_from_rgba_string(const char *str, short *ok) {
if (str == strstr(str, "rgba(")) {
str += 5;
WHITESPACE;
uint8_t r = 0, g = 0, b = 0;
float a = 1.f;
CHANNEL(r);
WHITESPACE_OR_COMMA;
CHANNEL(g);
WHITESPACE_OR_COMMA;
CHANNEL(b);
WHITESPACE_OR_COMMA;
ALPHA(a);
WHITESPACE;
return *ok = 1, rgba_from_rgba(r, g, b, (int) (a * 255));
}
return *ok = 0;
}
/*
* Return rgb from "hsla()"
*/
static int32_t
rgba_from_hsla_string(const char *str, short *ok) {
if (str == strstr(str, "hsla(")) {
str += 5;
WHITESPACE;
float h_deg = 0;
float s = 0, l = 0;
float a = 0;
HUE(h_deg);
WHITESPACE_OR_COMMA;
SATURATION(s);
WHITESPACE_OR_COMMA;
LIGHTNESS(l);
WHITESPACE_OR_COMMA;
ALPHA(a);
WHITESPACE;
return *ok = 1, rgba_from_hsla(h_deg, s, l, a);
}
return *ok = 0;
}
/*
* Return rgb from "hsl()"
*/
static int32_t
rgba_from_hsl_string(const char *str, short *ok) {
if (str == strstr(str, "hsl(")) {
str += 4;
WHITESPACE;
float h_deg = 0;
float s = 0, l = 0;
HUE(h_deg);
WHITESPACE_OR_COMMA;
SATURATION(s);
WHITESPACE_OR_COMMA;
LIGHTNESS(l);
WHITESPACE;
return *ok = 1, rgba_from_hsl(h_deg, s, l);
}
return *ok = 0;
}
/*
* Return rgb from:
*
* - "#RGB"
* - "#RGBA"
* - "#RRGGBB"
* - "#RRGGBBAA"
*
*/
static int32_t
rgba_from_hex_string(const char *str, short *ok) {
size_t len = strlen(str);
*ok = 1;
switch (len) {
case 8: return rgba_from_hex8_string(str);
case 6: return rgba_from_hex6_string(str);
case 4: return rgba_from_hex4_string(str);
case 3: return rgba_from_hex3_string(str);
}
return *ok = 0;
}
/*
* Return named color value.
*/
static int32_t
rgba_from_name_string(const char *str, short *ok) {
std::string lowered(str);
std::transform(lowered.begin(), lowered.end(), lowered.begin(), tolower);
auto color = named_colors.find(lowered);
if (color != named_colors.end()) {
return *ok = 1, color->second;
}
return *ok = 0;
}
/*
* Return rgb from:
*
* - #RGB
* - #RGBA
* - #RRGGBB
* - #RRGGBBAA
* - rgb(r,g,b)
* - rgba(r,g,b,a)
* - hsl(h,s,l)
* - hsla(h,s,l,a)
* - name
*
*/
int32_t
rgba_from_string(const char *str, short *ok) {
if ('#' == str[0])
return rgba_from_hex_string(++str, ok);
if (str == strstr(str, "rgba"))
return rgba_from_rgba_string(str, ok);
if (str == strstr(str, "rgb"))
return rgba_from_rgb_string(str, ok);
if (str == strstr(str, "hsla"))
return rgba_from_hsla_string(str, ok);
if (str == strstr(str, "hsl"))
return rgba_from_hsl_string(str, ok);
return rgba_from_name_string(str, ok);
}
/*
* Inspect the given rgba color.
*/
void
rgba_inspect(int32_t rgba) {
printf("rgba(%d,%d,%d,%d)\n"
, rgba >> 24 & 0xff
, rgba >> 16 & 0xff
, rgba >> 8 & 0xff
, rgba & 0xff
);
}

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#pragma once
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
#include <cstdlib>
/*
* RGBA struct.
*/
typedef struct {
double r, g, b, a;
} rgba_t;
/*
* Prototypes.
*/
rgba_t
rgba_create(uint32_t rgba);
int32_t
rgba_from_string(const char *str, short *ok);
void
rgba_to_string(rgba_t rgba, char *buf, size_t len);
void
rgba_inspect(int32_t rgba);

View File

@@ -0,0 +1,20 @@
#ifndef DLL_PUBLIC
#if defined _WIN32
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllexport))
#else
#define DLL_PUBLIC __declspec(dllexport)
#endif
#define DLL_LOCAL
#else
#if __GNUC__ >= 4
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define DLL_PUBLIC
#define DLL_LOCAL
#endif
#endif
#endif

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
#include <cstdio>
#include <pango/pango.h>
#include <cairo.h>
#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
// CAIRO_FORMAT_RGB16_565: undeprecated in v1.10.0
// CAIRO_STATUS_INVALID_SIZE: v1.10.0
// CAIRO_FORMAT_INVALID: v1.10.0
// Lots of the compositing operators: v1.10.0
// JPEG MIME tracking: v1.10.0
// Note: CAIRO_FORMAT_RGB30 is v1.12.0 and still optional
#error("cairo v1.10.0 or later is required")
#endif
#include "Backends.h"
#include "Canvas.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include "CanvasRenderingContext2d.h"
#include "Image.h"
#include "ImageData.h"
#include <ft2build.h>
#include FT_FREETYPE_H
using namespace v8;
// Compatibility with Visual Studio versions prior to VS2015
#if defined(_MSC_VER) && _MSC_VER < 1900
#define snprintf _snprintf
#endif
NAN_MODULE_INIT(init) {
Backends::Initialize(target);
Canvas::Initialize(target);
Image::Initialize(target);
ImageData::Initialize(target);
Context2d::Initialize(target);
Gradient::Initialize(target);
Pattern::Initialize(target);
Nan::Set(target, Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked()).Check();
#ifdef HAVE_JPEG
#ifndef JPEG_LIB_VERSION_MAJOR
#ifdef JPEG_LIB_VERSION
#define JPEG_LIB_VERSION_MAJOR (JPEG_LIB_VERSION / 10)
#else
#define JPEG_LIB_VERSION_MAJOR 0
#endif
#endif
#ifndef JPEG_LIB_VERSION_MINOR
#ifdef JPEG_LIB_VERSION
#define JPEG_LIB_VERSION_MINOR (JPEG_LIB_VERSION % 10)
#else
#define JPEG_LIB_VERSION_MINOR 0
#endif
#endif
char jpeg_version[10];
static bool minor_gt_0 = JPEG_LIB_VERSION_MINOR > 0;
if (minor_gt_0) {
snprintf(jpeg_version, 10, "%d%c", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR + 'a' - 1);
} else {
snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR);
}
Nan::Set(target, Nan::New<String>("jpegVersion").ToLocalChecked(), Nan::New<String>(jpeg_version).ToLocalChecked()).Check();
#endif
#ifdef HAVE_GIF
#ifndef GIF_LIB_VERSION
char gif_version[10];
snprintf(gif_version, 10, "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR, GIFLIB_RELEASE);
Nan::Set(target, Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(gif_version).ToLocalChecked()).Check();
#else
Nan::Set(target, Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(GIF_LIB_VERSION).ToLocalChecked()).Check();
#endif
#endif
#ifdef HAVE_RSVG
Nan::Set(target, Nan::New<String>("rsvgVersion").ToLocalChecked(), Nan::New<String>(LIBRSVG_VERSION).ToLocalChecked()).Check();
#endif
Nan::Set(target, Nan::New<String>("pangoVersion").ToLocalChecked(), Nan::New<String>(PANGO_VERSION_STRING).ToLocalChecked()).Check();
char freetype_version[10];
snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
Nan::Set(target, Nan::New<String>("freetypeVersion").ToLocalChecked(), Nan::New<String>(freetype_version).ToLocalChecked()).Check();
}
NODE_MODULE(canvas, init);

View File

@@ -0,0 +1,408 @@
#include "register_font.h"
#include <string>
#include <pango/pangocairo.h>
#include <pango/pango-fontmap.h>
#include <pango/pango.h>
#ifdef __APPLE__
#include <CoreText/CoreText.h>
#elif defined(_WIN32)
#include <windows.h>
#include <memory>
#else
#include <fontconfig/fontconfig.h>
#include <pango/pangofc-fontmap.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_IDS_H
#ifndef FT_SFNT_OS2
#define FT_SFNT_OS2 ft_sfnt_os2
#endif
// OSX seems to read the strings in MacRoman encoding and ignore Unicode entries.
// You can verify this by opening a TTF with both Unicode and Macroman on OSX.
// It uses the MacRoman name, while Fontconfig and Windows use Unicode
#ifdef __APPLE__
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH
#define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN
#else
#define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT
#define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS
#endif
// With PangoFcFontMaps (the pango font module on Linux) we're able to add a
// hook that lets us get perfect matching. Tie the conditions for enabling that
// feature to one variable
#if !defined(__APPLE__) && !defined(_WIN32) && PANGO_VERSION_CHECK(1, 47, 0)
#define PERFECT_MATCHES_ENABLED
#endif
#define IS_PREFERRED_ENC(X) \
X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
#ifdef PERFECT_MATCHES_ENABLED
// On Linux-like OSes using FontConfig, the PostScript name ranks higher than
// preferred family and family name since we'll use it to get perfect font
// matching (see fc_font_map_substitute_hook)
#define GET_NAME_RANK(X) \
((IS_PREFERRED_ENC(X) ? 1 : 0) << 2) | \
((X.name_id == TT_NAME_ID_PS_NAME ? 1 : 0) << 1) | \
(X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
#else
#define GET_NAME_RANK(X) \
((IS_PREFERRED_ENC(X) ? 1 : 0) << 1) | \
(X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
#endif
/*
* Return a UTF-8 encoded string given a TrueType name buf+len
* and its platform and encoding
*/
char *
to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) {
size_t ret_len = len * 4; // max chars in a utf8 string
char *ret = (char*)malloc(ret_len + 1); // utf8 string + null
if (!ret) return NULL;
// In my testing of hundreds of fonts from the Google Font repo, the two types
// of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or
// TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither
char const *fromcode;
if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) {
fromcode = "MAC";
} else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) {
fromcode = "UTF-16BE";
} else {
free(ret);
return NULL;
}
GIConv cd = g_iconv_open("UTF-8", fromcode);
if (cd == (GIConv)-1) {
free(ret);
return NULL;
}
size_t inbytesleft = len;
size_t outbytesleft = ret_len;
size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft);
ret -= ret_len - outbytesleft; // rewind the pointers to their
buf -= len - inbytesleft; // original starting positions
if (n_converted == (size_t)-1) {
free(ret);
return NULL;
} else {
ret[ret_len - outbytesleft] = '\0';
return ret;
}
}
/*
* Find a family name in the face's name table, preferring the one the
* system, fall back to the other
*/
char *
get_family_name(FT_Face face) {
FT_SfntName name;
int best_rank = -1;
char* best_buf = NULL;
for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) {
FT_Get_Sfnt_Name(face, i, &name);
if (
name.name_id == TT_NAME_ID_FONT_FAMILY ||
#ifdef PERFECT_MATCHES_ENABLED
name.name_id == TT_NAME_ID_PS_NAME ||
#endif
name.name_id == TT_NAME_ID_PREFERRED_FAMILY
) {
int rank = GET_NAME_RANK(name);
if (rank > best_rank) {
char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
if (buf) {
best_rank = rank;
if (best_buf) free(best_buf);
best_buf = buf;
#ifdef PERFECT_MATCHES_ENABLED
// Prepend an '@' to the postscript name
if (name.name_id == TT_NAME_ID_PS_NAME) {
std::string best_buf_modified = "@";
best_buf_modified += best_buf;
free(best_buf);
best_buf = strdup(best_buf_modified.c_str());
}
#endif
} else {
free(buf);
}
}
}
}
return best_buf;
}
PangoWeight
get_pango_weight(FT_UShort weight) {
switch (weight) {
case 100: return PANGO_WEIGHT_THIN;
case 200: return PANGO_WEIGHT_ULTRALIGHT;
case 300: return PANGO_WEIGHT_LIGHT;
#if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7)
case 350: return PANGO_WEIGHT_SEMILIGHT;
#endif
case 380: return PANGO_WEIGHT_BOOK;
case 400: return PANGO_WEIGHT_NORMAL;
case 500: return PANGO_WEIGHT_MEDIUM;
case 600: return PANGO_WEIGHT_SEMIBOLD;
case 700: return PANGO_WEIGHT_BOLD;
case 800: return PANGO_WEIGHT_ULTRABOLD;
case 900: return PANGO_WEIGHT_HEAVY;
case 1000: return PANGO_WEIGHT_ULTRAHEAVY;
default: return PANGO_WEIGHT_NORMAL;
}
}
PangoStretch
get_pango_stretch(FT_UShort width) {
switch (width) {
case 1: return PANGO_STRETCH_ULTRA_CONDENSED;
case 2: return PANGO_STRETCH_EXTRA_CONDENSED;
case 3: return PANGO_STRETCH_CONDENSED;
case 4: return PANGO_STRETCH_SEMI_CONDENSED;
case 5: return PANGO_STRETCH_NORMAL;
case 6: return PANGO_STRETCH_SEMI_EXPANDED;
case 7: return PANGO_STRETCH_EXPANDED;
case 8: return PANGO_STRETCH_EXTRA_EXPANDED;
case 9: return PANGO_STRETCH_ULTRA_EXPANDED;
default: return PANGO_STRETCH_NORMAL;
}
}
PangoStyle
get_pango_style(FT_Long flags) {
if (flags & FT_STYLE_FLAG_ITALIC) {
return PANGO_STYLE_ITALIC;
} else {
return PANGO_STYLE_NORMAL;
}
}
#ifdef _WIN32
std::unique_ptr<wchar_t[]>
u8ToWide(const char* str) {
int iBufferSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, (wchar_t*)NULL, 0);
if(!iBufferSize){
return nullptr;
}
std::unique_ptr<wchar_t[]> wpBufWString = std::unique_ptr<wchar_t[]>{ new wchar_t[static_cast<size_t>(iBufferSize)] };
if(!MultiByteToWideChar(CP_UTF8, 0, str, -1, wpBufWString.get(), iBufferSize)){
return nullptr;
}
return wpBufWString;
}
static unsigned long
stream_read_func(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count){
HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
DWORD numberOfBytesRead;
OVERLAPPED overlapped;
overlapped.Offset = offset;
overlapped.OffsetHigh = 0;
overlapped.hEvent = NULL;
if(!ReadFile(hFile, buffer, count, &numberOfBytesRead, &overlapped)){
return 0;
}
return numberOfBytesRead;
};
static void
stream_close_func(FT_Stream stream){
HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
CloseHandle(hFile);
}
#endif
/*
* Return a PangoFontDescription that will resolve to the font file
*/
PangoFontDescription *
get_pango_font_description(unsigned char* filepath) {
FT_Library library;
FT_Face face;
PangoFontDescription *desc = pango_font_description_new();
#ifdef _WIN32
// FT_New_Face use fopen.
// Unable to find the file when supplied the multibyte string path on the Windows platform and throw error "Could not parse font file".
// This workaround fixes this by reading the font file uses win32 wide character API.
std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
if(!wFilepath){
return NULL;
}
HANDLE hFile = CreateFileW(
wFilepath.get(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if(!hFile){
return NULL;
}
LARGE_INTEGER liSize;
if(!GetFileSizeEx(hFile, &liSize)) {
CloseHandle(hFile);
return NULL;
}
FT_Open_Args args;
args.flags = FT_OPEN_STREAM;
FT_StreamRec stream;
stream.base = NULL;
stream.size = liSize.QuadPart;
stream.pos = 0;
stream.descriptor.pointer = hFile;
stream.read = stream_read_func;
stream.close = stream_close_func;
args.stream = &stream;
if (
!FT_Init_FreeType(&library) &&
!FT_Open_Face(library, &args, 0, &face)) {
#else
if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) {
#endif
TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
if (table) {
char *family = get_family_name(face);
if (!family) {
pango_font_description_free(desc);
FT_Done_Face(face);
FT_Done_FreeType(library);
return NULL;
}
pango_font_description_set_family_static(desc, family);
pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass));
pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass));
pango_font_description_set_style(desc, get_pango_style(face->style_flags));
FT_Done_Face(face);
FT_Done_FreeType(library);
return desc;
}
}
pango_font_description_free(desc);
return NULL;
}
#ifdef PERFECT_MATCHES_ENABLED
static void
fc_font_map_substitute_hook(FcPattern *pat, gpointer data) {
FcChar8 *family;
for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &family) == FcResultMatch; i++) {
if (family[0] == '@') {
FcPatternAddString(pat, FC_POSTSCRIPT_NAME, (FcChar8 *)family + 1);
FcPatternRemove(pat, FC_FAMILY, i);
i -= 1;
}
}
}
#endif
/*
* Register font with the OS
*/
bool
register_font(unsigned char *filepath) {
bool success;
#ifdef __APPLE__
CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
#elif defined(_WIN32)
std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
if(wFilepath){
success = AddFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
}else{
success = false;
}
#else
success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
#endif
if (!success) return false;
// Tell Pango to throw away the current FontMap and create a new one. This
// has the effect of registering the new font in Pango by re-looking up all
// font families.
pango_cairo_font_map_set_default(NULL);
#ifdef PERFECT_MATCHES_ENABLED
PangoFontMap* map = pango_cairo_font_map_get_default();
PangoFcFontMap* fc_map = PANGO_FC_FONT_MAP(map);
pango_fc_font_map_set_default_substitute(fc_map, fc_font_map_substitute_hook, NULL, NULL);
#endif
return true;
}
/*
* Deregister font from the OS
* Note that Linux (FontConfig) can only dereregister ALL fonts at once.
*/
bool
deregister_font(unsigned char *filepath) {
bool success;
#ifdef __APPLE__
CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
success = CTFontManagerUnregisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
#elif defined(_WIN32)
std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
if(wFilepath){
success = RemoveFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
}else{
success = false;
}
#else
FcConfigAppFontClear(FcConfigGetCurrent());
success = true;
#endif
if (!success) return false;
// Tell Pango to throw away the current FontMap and create a new one. This
// has the effect of deregistering the font in Pango by re-looking up all
// font families.
pango_cairo_font_map_set_default(NULL);
return true;
}

View File

@@ -0,0 +1,7 @@
#pragma once
#include <pango/pango.h>
PangoFontDescription *get_pango_font_description(unsigned char *filepath);
bool register_font(unsigned char *filepath);
bool deregister_font(unsigned char *filepath);