Initial commit
This commit is contained in:
commit
e49726988c
5445 files changed
+1089914
No files matched your search
+18
@@ -0,0 +1,18 @@
|
||||
#include "Backends.h"
|
||||
|
||||
#include "backend/ImageBackend.h"
|
||||
#include "backend/PdfBackend.h"
|
||||
#include "backend/SvgBackend.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
void
|
||||
Backends::Initialize(Napi::Env env, Napi::Object exports) {
|
||||
Napi::Object obj = Napi::Object::New(env);
|
||||
|
||||
ImageBackend::Initialize(obj);
|
||||
PdfBackend::Initialize(obj);
|
||||
SvgBackend::Initialize(obj);
|
||||
|
||||
exports.Set("Backends", obj);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "backend/Backend.h"
|
||||
#include <napi.h>
|
||||
|
||||
class Backends : public Napi::ObjectWrap<Backends> {
|
||||
public:
|
||||
static void Initialize(Napi::Env env, Napi::Object exports);
|
||||
};
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "InstanceData.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"
|
||||
#include "FontParser.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)."
|
||||
|
||||
using namespace std;
|
||||
|
||||
std::vector<FontFace> Canvas::font_face_list;
|
||||
|
||||
// Increases each time a font is (de)registered
|
||||
int Canvas::fontSerial = 1;
|
||||
|
||||
/*
|
||||
* Initialize Canvas.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
// Constructor
|
||||
Napi::Function ctor = DefineClass(env, "Canvas", {
|
||||
InstanceMethod<&Canvas::ToBuffer>("toBuffer", napi_default_method),
|
||||
InstanceMethod<&Canvas::StreamPNGSync>("streamPNGSync", napi_default_method),
|
||||
InstanceMethod<&Canvas::StreamPDFSync>("streamPDFSync", napi_default_method),
|
||||
#ifdef HAVE_JPEG
|
||||
InstanceMethod<&Canvas::StreamJPEGSync>("streamJPEGSync", napi_default_method),
|
||||
#endif
|
||||
InstanceAccessor<&Canvas::GetType>("type", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetStride>("stride", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetWidth, &Canvas::SetWidth>("width", napi_default_jsproperty),
|
||||
InstanceAccessor<&Canvas::GetHeight, &Canvas::SetHeight>("height", napi_default_jsproperty),
|
||||
StaticValue("PNG_NO_FILTERS", Napi::Number::New(env, PNG_NO_FILTERS), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_NONE", Napi::Number::New(env, PNG_FILTER_NONE), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_SUB", Napi::Number::New(env, PNG_FILTER_SUB), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_UP", Napi::Number::New(env, PNG_FILTER_UP), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_AVG", Napi::Number::New(env, PNG_FILTER_AVG), napi_default_jsproperty),
|
||||
StaticValue("PNG_FILTER_PAETH", Napi::Number::New(env, PNG_FILTER_PAETH), napi_default_jsproperty),
|
||||
StaticValue("PNG_ALL_FILTERS", Napi::Number::New(env, PNG_ALL_FILTERS), napi_default_jsproperty),
|
||||
StaticMethod<&Canvas::RegisterFont>("_registerFont", napi_default_method),
|
||||
StaticMethod<&Canvas::DeregisterAllFonts>("_deregisterAllFonts", napi_default_method),
|
||||
StaticMethod<&Canvas::ParseFont>("parseFont", napi_default_method)
|
||||
});
|
||||
|
||||
data->CanvasCtor = Napi::Persistent(ctor);
|
||||
exports.Set("Canvas", ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a Canvas with the given width and height.
|
||||
*/
|
||||
|
||||
Canvas::Canvas(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Canvas>(info), env(info.Env()) {
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
ctor = Napi::Persistent(data->CanvasCtor.Value());
|
||||
Backend* backend = NULL;
|
||||
Napi::Object jsBackend;
|
||||
|
||||
if (info[0].IsNumber()) {
|
||||
Napi::Number width = info[0].As<Napi::Number>();
|
||||
Napi::Number height = Napi::Number::New(env, 0);
|
||||
|
||||
if (info[1].IsNumber()) height = info[1].As<Napi::Number>();
|
||||
|
||||
if (info[2].IsString()) {
|
||||
std::string str = info[2].As<Napi::String>();
|
||||
if (str == "pdf") {
|
||||
Napi::Maybe<Napi::Object> instance = data->PdfBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = PdfBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
} else if (str == "svg") {
|
||||
Napi::Maybe<Napi::Object> instance = data->SvgBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = SvgBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
} else {
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
} else {
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
} else if (info[0].IsObject()) {
|
||||
jsBackend = info[0].As<Napi::Object>();
|
||||
if (jsBackend.InstanceOf(data->ImageBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = ImageBackend::Unwrap(jsBackend);
|
||||
} else if (jsBackend.InstanceOf(data->PdfBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = PdfBackend::Unwrap(jsBackend);
|
||||
} else if (jsBackend.InstanceOf(data->SvgBackendCtor.Value()).UnwrapOr(false)) {
|
||||
backend = SvgBackend::Unwrap(jsBackend);
|
||||
} else {
|
||||
Napi::TypeError::New(env, "Invalid arguments").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Napi::Number width = Napi::Number::New(env, 0);
|
||||
Napi::Number height = Napi::Number::New(env, 0);
|
||||
Napi::Maybe<Napi::Object> instance = data->ImageBackendCtor.New({ width, height });
|
||||
if (instance.IsJust()) backend = ImageBackend::Unwrap(jsBackend = instance.Unwrap());
|
||||
}
|
||||
|
||||
if (!backend->isSurfaceValid()) {
|
||||
Napi::Error::New(env, backend->getError()).ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
backend->setCanvas(this);
|
||||
|
||||
// Note: the backend gets destroyed when the jsBackend is GC'd. The cleaner
|
||||
// way would be to only store the jsBackend and unwrap it when the c++ ref is
|
||||
// needed, but that's slower and a burden. The _backend might be null if we
|
||||
// returned early, but since an exception was thrown it gets destroyed soon.
|
||||
_backend = backend;
|
||||
_jsBackend = Napi::Persistent(jsBackend);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get type string.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetType(const Napi::CallbackInfo& info) {
|
||||
return Napi::String::New(env, backend()->getName());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get stride.
|
||||
*/
|
||||
Napi::Value
|
||||
Canvas::GetStride(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, stride());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetWidth(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, getWidth());
|
||||
}
|
||||
|
||||
/*
|
||||
* Set width.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
||||
if (value.IsNumber()) {
|
||||
backend()->setWidth(value.As<Napi::Number>().Uint32Value());
|
||||
resurface(info.This().As<Napi::Object>());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::GetHeight(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, getHeight());
|
||||
}
|
||||
|
||||
/*
|
||||
* Set height.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value) {
|
||||
if (value.IsNumber()) {
|
||||
backend()->setHeight(value.As<Napi::Number>().Uint32Value());
|
||||
resurface(info.This().As<Napi::Object>());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EIO toBuffer callback.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::ToPngBufferAsync(Closure* base) {
|
||||
PngClosure* closure = static_cast<PngClosure*>(base);
|
||||
|
||||
closure->status = canvas_write_to_png_stream(
|
||||
closure->canvas->surface(),
|
||||
PngClosure::writeVec,
|
||||
closure);
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
void
|
||||
Canvas::ToJpegBufferAsync(Closure* base) {
|
||||
JpegClosure* closure = static_cast<JpegClosure*>(base);
|
||||
write_to_jpeg_buffer(closure->canvas->surface(), closure);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
parsePNGArgs(Napi::Value arg, PngClosure& pngargs) {
|
||||
if (arg.IsObject()) {
|
||||
Napi::Object obj = arg.As<Napi::Object>();
|
||||
Napi::Value cLevel;
|
||||
|
||||
if (obj.Get("compressionLevel").UnwrapTo(&cLevel) && cLevel.IsNumber()) {
|
||||
uint32_t val = cLevel.As<Napi::Number>().Uint32Value();
|
||||
// See quote below from spec section 4.12.5.5.
|
||||
if (val <= 9) pngargs.compressionLevel = val;
|
||||
}
|
||||
|
||||
Napi::Value rez;
|
||||
if (obj.Get("resolution").UnwrapTo(&rez) && rez.IsNumber()) {
|
||||
uint32_t val = rez.As<Napi::Number>().Uint32Value();
|
||||
if (val > 0) pngargs.resolution = val;
|
||||
}
|
||||
|
||||
Napi::Value filters;
|
||||
if (obj.Get("filters").UnwrapTo(&filters) && filters.IsNumber()) {
|
||||
pngargs.filters = filters.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
|
||||
Napi::Value palette;
|
||||
if (obj.Get("palette").UnwrapTo(&palette) && palette.IsTypedArray()) {
|
||||
Napi::TypedArray palette_ta = palette.As<Napi::TypedArray>();
|
||||
if (palette_ta.TypedArrayType() == napi_uint8_clamped_array) {
|
||||
pngargs.nPaletteColors = palette_ta.ElementLength();
|
||||
if (pngargs.nPaletteColors % 4 != 0) {
|
||||
throw "Palette length must be a multiple of 4.";
|
||||
}
|
||||
pngargs.palette = palette_ta.As<Napi::Uint8Array>().Data();
|
||||
pngargs.nPaletteColors /= 4;
|
||||
// Optional background color index:
|
||||
Napi::Value backgroundIndexVal;
|
||||
if (obj.Get("backgroundIndex").UnwrapTo(&backgroundIndexVal) && backgroundIndexVal.IsNumber()) {
|
||||
pngargs.backgroundIndex = backgroundIndexVal.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
static void parseJPEGArgs(Napi::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()) {
|
||||
Napi::Object obj = arg.As<Napi::Object>();
|
||||
|
||||
Napi::Value qual;
|
||||
if (obj.Get("quality").UnwrapTo(&qual) && qual.IsNumber()) {
|
||||
double quality = qual.As<Napi::Number>().DoubleValue();
|
||||
if (quality >= 0.0 && quality <= 1.0) {
|
||||
jpegargs.quality = static_cast<uint32_t>(100.0 * quality);
|
||||
}
|
||||
}
|
||||
|
||||
Napi::Value chroma;
|
||||
if (obj.Get("chromaSubsampling").UnwrapTo(&chroma)) {
|
||||
if (chroma.IsBoolean()) {
|
||||
bool subsample = chroma.As<Napi::Boolean>().Value();
|
||||
jpegargs.chromaSubsampling = subsample ? 2 : 1;
|
||||
} else if (chroma.IsNumber()) {
|
||||
jpegargs.chromaSubsampling = chroma.As<Napi::Number>().Uint32Value();
|
||||
}
|
||||
}
|
||||
|
||||
Napi::Value progressive;
|
||||
if (obj.Get("progressive").UnwrapTo(&progressive) && progressive.IsBoolean()) {
|
||||
jpegargs.progressive = progressive.As<Napi::Boolean>().Value();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
|
||||
static inline void setPdfMetaStr(cairo_surface_t* surf, Napi::Object opts,
|
||||
cairo_pdf_metadata_t t, const char* propName) {
|
||||
Napi::Value propValue;
|
||||
if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsString()) {
|
||||
// (copies char data)
|
||||
cairo_pdf_surface_set_metadata(surf, t, propValue.As<Napi::String>().Utf8Value().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static inline void setPdfMetaDate(cairo_surface_t* surf, Napi::Object opts,
|
||||
cairo_pdf_metadata_t t, const char* propName) {
|
||||
Napi::Value propValue;
|
||||
if (opts.Get(propName).UnwrapTo(&propValue) && propValue.IsDate()) {
|
||||
auto date = static_cast<time_t>(propValue.As<Napi::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, Napi::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})
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
Canvas::ToBuffer(const Napi::CallbackInfo& info) {
|
||||
EncodingWorker *worker = new EncodingWorker(info.Env());
|
||||
cairo_status_t status;
|
||||
|
||||
// Vector canvases, sync only
|
||||
const std::string name = 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*>(backend())->closure();
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
if (info[1].IsObject()) { // toBuffer("application/pdf", config)
|
||||
setPdfMetadata(this, info[1].As<Napi::Object>());
|
||||
}
|
||||
#endif // CAIRO 16+
|
||||
} else {
|
||||
closure = static_cast<SvgBackend*>(backend())->closure();
|
||||
}
|
||||
|
||||
cairo_surface_t *surf = surface();
|
||||
cairo_surface_finish(surf);
|
||||
|
||||
cairo_status_t status = cairo_surface_status(surf);
|
||||
if (status != CAIRO_STATUS_SUCCESS) {
|
||||
Napi::Error::New(env, cairo_status_to_string(status)).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure->vec[0], closure->vec.size());
|
||||
}
|
||||
|
||||
// Raw ARGB data -- just a memcpy()
|
||||
if (info[0].StrictEquals(Napi::String::New(env, "raw"))) {
|
||||
cairo_surface_t *surface = this->surface();
|
||||
cairo_surface_flush(surface);
|
||||
if (nBytes() > node::Buffer::kMaxLength) {
|
||||
Napi::Error::New(env, "Data exceeds maximum buffer length.").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
return Napi::Buffer<uint8_t>::Copy(env, cairo_image_surface_get_data(surface), nBytes());
|
||||
}
|
||||
|
||||
// Sync PNG, default
|
||||
if (info[0].IsUndefined() || info[0].StrictEquals(Napi::String::New(env, "image/png"))) {
|
||||
try {
|
||||
PngClosure closure(this);
|
||||
parsePNGArgs(info[1], closure);
|
||||
if (closure.nPaletteColors == 0xFFFFFFFF) {
|
||||
Napi::Error::New(env, "Palette length must be a multiple of 4.").ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
status = canvas_write_to_png_stream(surface(), PngClosure::writeVec, &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
throw status; // TODO: throw in js?
|
||||
} else {
|
||||
// TODO it's possible to avoid this copy
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure.vec[0], closure.vec.size());
|
||||
}
|
||||
}
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
} catch (const char* ex) {
|
||||
Napi::Error::New(env, ex).ThrowAsJavaScriptException();
|
||||
|
||||
}
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
// Async PNG
|
||||
if (info[0].IsFunction() &&
|
||||
(info[1].IsUndefined() || info[1].StrictEquals(Napi::String::New(env, "image/png")))) {
|
||||
|
||||
PngClosure* closure;
|
||||
try {
|
||||
closure = new PngClosure(this);
|
||||
parsePNGArgs(info[2], *closure);
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
} catch (const char* ex) {
|
||||
Napi::Error::New(env, ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
Ref();
|
||||
closure->cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
// Make sure the surface exists since we won't have an isolate context in the async block:
|
||||
surface();
|
||||
worker->Init(&ToPngBufferAsync, closure);
|
||||
worker->Queue();
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
// Sync JPEG
|
||||
Napi::Value jpegStr = Napi::String::New(env, "image/jpeg");
|
||||
if (info[0].StrictEquals(jpegStr)) {
|
||||
try {
|
||||
JpegClosure closure(this);
|
||||
parseJPEGArgs(info[1], closure);
|
||||
|
||||
write_to_jpeg_buffer(surface(), &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
// TODO it's possible to avoid this copy.
|
||||
return Napi::Buffer<uint8_t>::Copy(env, &closure.vec[0], closure.vec.size());
|
||||
}
|
||||
} catch (cairo_status_t ex) {
|
||||
CairoError(ex).ThrowAsJavaScriptException();
|
||||
return env.Undefined();
|
||||
}
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
// Async JPEG
|
||||
if (info[0].IsFunction() && info[1].StrictEquals(jpegStr)) {
|
||||
JpegClosure* closure = new JpegClosure(this);
|
||||
parseJPEGArgs(info[2], *closure);
|
||||
|
||||
Ref();
|
||||
closure->cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
// Make sure the surface exists since we won't have an isolate context in the async block:
|
||||
surface();
|
||||
worker->Init(&ToJpegBufferAsync, closure);
|
||||
worker->Queue();
|
||||
return env.Undefined();
|
||||
}
|
||||
#endif
|
||||
|
||||
return env.Undefined();
|
||||
}
|
||||
|
||||
/*
|
||||
* Canvas::StreamPNG callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPNG(void *c, const uint8_t *data, unsigned len) {
|
||||
PngClosure* closure = (PngClosure*) c;
|
||||
Napi::Env env = closure->canvas->env;
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:StreamPNG");
|
||||
Napi::Value buf = Napi::Buffer<uint8_t>::Copy(env, data, len);
|
||||
closure->cb.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async);
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream PNG data synchronously. TODO async
|
||||
* StreamPngSync(this, options: {palette?: Uint8ClampedArray, backgroundIndex?: uint32, compressionLevel: uint32, filters: uint32})
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::StreamPNGSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
PngClosure closure(this);
|
||||
parsePNGArgs(info[1], closure);
|
||||
|
||||
closure.cb = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
|
||||
cairo_status_t status = canvas_write_to_png_stream(surface(), streamPNG, &closure);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
closure.cb.Call(env.Global(), { CairoError(status).Value() });
|
||||
} else {
|
||||
closure.cb.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PdfStreamInfo {
|
||||
Napi::Function fn;
|
||||
uint32_t len;
|
||||
uint8_t* data;
|
||||
};
|
||||
|
||||
/*
|
||||
* Canvas::StreamPDF callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPDF(void *c, const uint8_t *data, unsigned len) {
|
||||
PdfStreamInfo* streaminfo = static_cast<PdfStreamInfo*>(c);
|
||||
Napi::Env env = streaminfo->fn.Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:StreamPDF");
|
||||
// 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.
|
||||
Napi::Value buf = Napi::Buffer<uint8_t>::New(env, (uint8_t *)(data), len);
|
||||
streaminfo->fn.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async);
|
||||
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.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::StreamPDFSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (backend()->getName() != "pdf") {
|
||||
Napi::TypeError::New(env, "wrong canvas type").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
if (info[1].IsObject()) {
|
||||
setPdfMetadata(this, info[1].As<Napi::Object>());
|
||||
}
|
||||
#endif
|
||||
|
||||
cairo_surface_finish(surface());
|
||||
|
||||
PdfSvgClosure* closure = static_cast<PdfBackend*>(backend())->closure();
|
||||
Napi::Function fn = info[0].As<Napi::Function>();
|
||||
PdfStreamInfo streaminfo;
|
||||
streaminfo.fn = fn;
|
||||
streaminfo.data = &closure->vec[0];
|
||||
streaminfo.len = closure->vec.size();
|
||||
|
||||
cairo_status_t status = canvas_write_to_pdf_stream(surface(), streamPDF, &streaminfo);
|
||||
|
||||
if (!env.IsExceptionPending()) {
|
||||
if (status) {
|
||||
fn.Call(env.Global(), { CairoError(status).Value() });
|
||||
} else {
|
||||
fn.Call(env.Global(), { env.Null(), env.Null(), Napi::Number::New(env, 0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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));
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::StreamJPEGSync(const Napi::CallbackInfo& info) {
|
||||
if (!info[1].IsFunction()) {
|
||||
Napi::TypeError::New(env, "callback function required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
JpegClosure closure(this);
|
||||
parseJPEGArgs(info[0], closure);
|
||||
closure.cb = Napi::Persistent(info[1].As<Napi::Function>());
|
||||
|
||||
uint32_t bufsize = getSafeBufSize(this);
|
||||
write_to_jpeg_stream(surface(), bufsize, &closure);
|
||||
}
|
||||
#endif
|
||||
|
||||
char *
|
||||
str_value(Napi::Maybe<Napi::Value> maybe, const char *fallback, bool can_be_number) {
|
||||
Napi::Value val;
|
||||
if (maybe.UnwrapTo(&val)) {
|
||||
if (val.IsString() || (can_be_number && val.IsNumber())) {
|
||||
Napi::String strVal;
|
||||
if (val.ToString().UnwrapTo(&strVal)) return strdup(strVal.Utf8Value().c_str());
|
||||
} else if (fallback) {
|
||||
return strdup(fallback);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::RegisterFont(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
if (!info[0].IsString()) {
|
||||
Napi::Error::New(env, "Wrong argument type").ThrowAsJavaScriptException();
|
||||
return;
|
||||
} else if (!info[1].IsObject()) {
|
||||
Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filePath = info[0].As<Napi::String>();
|
||||
PangoFontDescription *sys_desc = get_pango_font_description((unsigned char *)(filePath.c_str()));
|
||||
|
||||
if (!sys_desc) {
|
||||
Napi::Error::New(env, "Could not parse font file").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
PangoFontDescription *user_desc = pango_font_description_new();
|
||||
|
||||
// now check the attrs, there are many ways to be wrong
|
||||
Napi::Object js_user_desc = info[1].As<Napi::Object>();
|
||||
|
||||
// TODO: use FontParser on these values just like the FontFace API works
|
||||
char *family = str_value(js_user_desc.Get("family"), NULL, false);
|
||||
char *weight = str_value(js_user_desc.Get("weight"), "normal", true);
|
||||
char *style = str_value(js_user_desc.Get("style"), "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.c_str())) {
|
||||
FontFace face;
|
||||
face.user_desc = user_desc;
|
||||
face.sys_desc = sys_desc;
|
||||
strncpy((char *)face.file_path, (char *) filePath.c_str(), 1023);
|
||||
font_face_list.push_back(face);
|
||||
} else {
|
||||
pango_font_description_free(user_desc);
|
||||
Napi::Error::New(env, "Could not load font to the system's font host").ThrowAsJavaScriptException();
|
||||
|
||||
}
|
||||
} else {
|
||||
pango_font_description_free(user_desc);
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::Error::New(env, GENERIC_FACE_ERROR).ThrowAsJavaScriptException();
|
||||
}
|
||||
}
|
||||
|
||||
free(family);
|
||||
free(weight);
|
||||
free(style);
|
||||
fontSerial++;
|
||||
}
|
||||
|
||||
void
|
||||
Canvas::DeregisterAllFonts(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
// 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();
|
||||
fontSerial++;
|
||||
if (!success) Napi::Error::New(env, "Could not deregister one or more fonts").ThrowAsJavaScriptException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not use! This is only exported for testing
|
||||
*/
|
||||
Napi::Value
|
||||
Canvas::ParseFont(const Napi::CallbackInfo& info) {
|
||||
Napi::Env env = info.Env();
|
||||
|
||||
if (info.Length() != 1) return env.Undefined();
|
||||
|
||||
Napi::String str;
|
||||
if (!info[0].ToString().UnwrapTo(&str)) return env.Undefined();
|
||||
|
||||
bool ok;
|
||||
auto props = FontParser::parse(str, &ok);
|
||||
if (!ok) return env.Undefined();
|
||||
|
||||
Napi::Object obj = Napi::Object::New(env);
|
||||
obj.Set("size", Napi::Number::New(env, props.fontSize));
|
||||
Napi::Array families = Napi::Array::New(env);
|
||||
obj.Set("families", families);
|
||||
|
||||
unsigned int index = 0;
|
||||
|
||||
for (auto& family : props.fontFamily) {
|
||||
families[index++] = Napi::String::New(env, family);
|
||||
}
|
||||
|
||||
obj.Set("weight", Napi::Number::New(env, props.fontWeight));
|
||||
obj.Set("variant", Napi::Number::New(env, static_cast<int>(props.fontVariant)));
|
||||
obj.Set("style", Napi::Number::New(env, static_cast<int>(props.fontStyle)));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(Napi::Object This) {
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::Value context;
|
||||
|
||||
if (This.Get("context").UnwrapTo(&context) && context.IsObject()) {
|
||||
backend()->recreateSurface();
|
||||
// Reset context
|
||||
Context2d *context2d = Context2d::Unwrap(context.As<Napi::Object>());
|
||||
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.
|
||||
*/
|
||||
|
||||
Napi::Error
|
||||
Canvas::CairoError(cairo_status_t status) {
|
||||
return Napi::Error::New(env, cairo_status_to_string(status));
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
struct Closure;
|
||||
|
||||
#include "backend/Backend.h"
|
||||
#include "closure.h"
|
||||
#include <cairo.h>
|
||||
#include "dll_visibility.h"
|
||||
#include <napi.h>
|
||||
#include <pango/pangocairo.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,
|
||||
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 Napi::ObjectWrap<Canvas> {
|
||||
public:
|
||||
Canvas(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
|
||||
Napi::Value ToBuffer(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetType(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetStride(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void StreamPNGSync(const Napi::CallbackInfo& info);
|
||||
void StreamPDFSync(const Napi::CallbackInfo& info);
|
||||
void StreamJPEGSync(const Napi::CallbackInfo& info);
|
||||
static void RegisterFont(const Napi::CallbackInfo& info);
|
||||
static void DeregisterAllFonts(const Napi::CallbackInfo& info);
|
||||
static Napi::Value ParseFont(const Napi::CallbackInfo& info);
|
||||
Napi::Error CairoError(cairo_status_t status);
|
||||
static void ToPngBufferAsync(Closure* closure);
|
||||
static void ToJpegBufferAsync(Closure* closure);
|
||||
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>(backend()->getHeight()) * stride();
|
||||
}
|
||||
|
||||
DLL_PUBLIC inline int getWidth() { return backend()->getWidth(); }
|
||||
DLL_PUBLIC inline int getHeight() { return backend()->getHeight(); }
|
||||
|
||||
void resurface(Napi::Object This);
|
||||
|
||||
Napi::Env env;
|
||||
static int fontSerial;
|
||||
|
||||
private:
|
||||
Backend* _backend;
|
||||
Napi::ObjectReference _jsBackend;
|
||||
Napi::FunctionReference ctor;
|
||||
static std::vector<FontFace> font_face_list;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <napi.h>
|
||||
|
||||
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;
|
||||
}
|
||||
bool empty() {
|
||||
return cerrno == 0 && message.empty();
|
||||
}
|
||||
Napi::Error toError(Napi::Env env) {
|
||||
if (cerrno) {
|
||||
Napi::Error err = Napi::Error::New(env, strerror(cerrno));
|
||||
if (!syscall.empty()) err.Value().Set("syscall", syscall);
|
||||
if (!path.empty()) err.Value().Set("path", path);
|
||||
return err;
|
||||
} else {
|
||||
return Napi::Error::New(env, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "CanvasGradient.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "color.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
/*
|
||||
* Initialize CanvasGradient.
|
||||
*/
|
||||
|
||||
void
|
||||
Gradient::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
Napi::Function ctor = DefineClass(env, "CanvasGradient", {
|
||||
InstanceMethod<&Gradient::AddColorStop>("addColorStop", napi_default_method)
|
||||
});
|
||||
|
||||
exports.Set("CanvasGradient", ctor);
|
||||
data->CanvasGradientCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasGradient.
|
||||
*/
|
||||
|
||||
Gradient::Gradient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Gradient>(info), env(info.Env()) {
|
||||
// Linear
|
||||
if (
|
||||
4 == info.Length() &&
|
||||
info[0].IsNumber() &&
|
||||
info[1].IsNumber() &&
|
||||
info[2].IsNumber() &&
|
||||
info[3].IsNumber()
|
||||
) {
|
||||
double x0 = info[0].As<Napi::Number>().DoubleValue();
|
||||
double y0 = info[1].As<Napi::Number>().DoubleValue();
|
||||
double x1 = info[2].As<Napi::Number>().DoubleValue();
|
||||
double y1 = info[3].As<Napi::Number>().DoubleValue();
|
||||
_pattern = cairo_pattern_create_linear(x0, y0, x1, y1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Radial
|
||||
if (
|
||||
6 == info.Length() &&
|
||||
info[0].IsNumber() &&
|
||||
info[1].IsNumber() &&
|
||||
info[2].IsNumber() &&
|
||||
info[3].IsNumber() &&
|
||||
info[4].IsNumber() &&
|
||||
info[5].IsNumber()
|
||||
) {
|
||||
double x0 = info[0].As<Napi::Number>().DoubleValue();
|
||||
double y0 = info[1].As<Napi::Number>().DoubleValue();
|
||||
double r0 = info[2].As<Napi::Number>().DoubleValue();
|
||||
double x1 = info[3].As<Napi::Number>().DoubleValue();
|
||||
double y1 = info[4].As<Napi::Number>().DoubleValue();
|
||||
double r1 = info[5].As<Napi::Number>().DoubleValue();
|
||||
_pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::TypeError::New(env, "invalid arguments").ThrowAsJavaScriptException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Add color stop.
|
||||
*/
|
||||
|
||||
void
|
||||
Gradient::AddColorStop(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsNumber()) {
|
||||
Napi::TypeError::New(env, "offset required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!info[1].IsString()) {
|
||||
Napi::TypeError::New(env, "color string required").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
short ok;
|
||||
std::string str = info[1].As<Napi::String>();
|
||||
uint32_t rgba = rgba_from_string(str.c_str(), &ok);
|
||||
|
||||
if (ok) {
|
||||
rgba_t color = rgba_create(rgba);
|
||||
cairo_pattern_add_color_stop_rgba(
|
||||
_pattern
|
||||
, info[0].As<Napi::Number>().DoubleValue()
|
||||
, color.r
|
||||
, color.g
|
||||
, color.b
|
||||
, color.a);
|
||||
} else {
|
||||
Napi::TypeError::New(env, "parse color failed").ThrowAsJavaScriptException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Destroy the pattern.
|
||||
*/
|
||||
|
||||
Gradient::~Gradient() {
|
||||
if (_pattern) cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <napi.h>
|
||||
#include <cairo.h>
|
||||
|
||||
class Gradient : public Napi::ObjectWrap<Gradient> {
|
||||
public:
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
Gradient(const Napi::CallbackInfo& info);
|
||||
void AddColorStop(const Napi::CallbackInfo& info);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
~Gradient();
|
||||
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
cairo_pattern_t *_pattern;
|
||||
};
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "CanvasPattern.h"
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "Image.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
const cairo_user_data_key_t *pattern_repeat_key;
|
||||
|
||||
/*
|
||||
* Initialize CanvasPattern.
|
||||
*/
|
||||
|
||||
void
|
||||
Pattern::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
// Constructor
|
||||
Napi::Function ctor = DefineClass(env, "CanvasPattern", {
|
||||
InstanceMethod<&Pattern::setTransform>("setTransform", napi_default_method)
|
||||
});
|
||||
|
||||
// Prototype
|
||||
exports.Set("CanvasPattern", ctor);
|
||||
data->CanvasPatternCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasPattern.
|
||||
*/
|
||||
|
||||
Pattern::Pattern(const Napi::CallbackInfo& info) : ObjectWrap<Pattern>(info), env(info.Env()) {
|
||||
if (!info[0].IsObject()) {
|
||||
Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Object obj = info[0].As<Napi::Object>();
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
cairo_surface_t *surface;
|
||||
|
||||
// Image
|
||||
if (obj.InstanceOf(data->ImageCtor.Value()).UnwrapOr(false)) {
|
||||
Image *img = Image::Unwrap(obj);
|
||||
if (!img->isComplete()) {
|
||||
Napi::Error::New(env, "Image given has not completed loading").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
surface = img->surface();
|
||||
|
||||
// Canvas
|
||||
} else if (obj.InstanceOf(data->CanvasCtor.Value()).UnwrapOr(false)) {
|
||||
Canvas *canvas = Canvas::Unwrap(obj);
|
||||
surface = canvas->surface();
|
||||
// Invalid
|
||||
} else {
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::TypeError::New(env, "Image or Canvas expected").ThrowAsJavaScriptException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_pattern = cairo_pattern_create_for_surface(surface);
|
||||
|
||||
if (info[1].IsString()) {
|
||||
if ("no-repeat" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = NO_REPEAT;
|
||||
} else if ("repeat-x" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = REPEAT_X;
|
||||
} else if ("repeat-y" == info[1].As<Napi::String>().Utf8Value()) {
|
||||
_repeat = REPEAT_Y;
|
||||
}
|
||||
}
|
||||
|
||||
cairo_pattern_set_user_data(_pattern, pattern_repeat_key, &_repeat, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the pattern-space to user-space transform.
|
||||
*/
|
||||
void
|
||||
Pattern::setTransform(const Napi::CallbackInfo& info) {
|
||||
if (!info[0].IsObject()) {
|
||||
Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Object mat = info[0].As<Napi::Object>();
|
||||
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
if (!mat.InstanceOf(data->DOMMatrixCtor.Value()).UnwrapOr(false)) {
|
||||
if (!env.IsExceptionPending()) {
|
||||
Napi::TypeError::New(env, "Expected DOMMatrix").ThrowAsJavaScriptException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Napi::Value one = Napi::Number::New(env, 1);
|
||||
Napi::Value zero = Napi::Number::New(env, 0);
|
||||
|
||||
cairo_matrix_t matrix;
|
||||
cairo_matrix_init(&matrix,
|
||||
mat.Get("a").UnwrapOr(one).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("b").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("c").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("d").UnwrapOr(one).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("e").UnwrapOr(zero).As<Napi::Number>().DoubleValue(),
|
||||
mat.Get("f").UnwrapOr(zero).As<Napi::Number>().DoubleValue()
|
||||
);
|
||||
|
||||
cairo_matrix_invert(&matrix);
|
||||
cairo_pattern_set_matrix(_pattern, &matrix);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (_pattern) cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2011 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include <napi.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 Napi::ObjectWrap<Pattern> {
|
||||
public:
|
||||
Pattern(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
void setTransform(const Napi::CallbackInfo& info);
|
||||
static repeat_type_t get_repeat_type_for_cairo_pattern(cairo_pattern_t *pattern);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
~Pattern();
|
||||
Napi::Env env;
|
||||
private:
|
||||
cairo_pattern_t *_pattern;
|
||||
repeat_type_t _repeat = REPEAT;
|
||||
};
|
||||
+3468
File diff suppressed because it is too large.
Load diff
+232
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cairo.h"
|
||||
#include "Canvas.h"
|
||||
#include "color.h"
|
||||
#include "napi.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_START;
|
||||
text_baseline_t textBaseline = TEXT_BASELINE_ALPHABETIC;
|
||||
canvas_draw_mode_t textDrawingMode = TEXT_DRAW_PATHS;
|
||||
bool imageSmoothingEnabled = true;
|
||||
std::string direction = "ltr";
|
||||
|
||||
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(const Napi::CallbackInfo& info);
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
void DrawImage(const Napi::CallbackInfo& info);
|
||||
void PutImageData(const Napi::CallbackInfo& info);
|
||||
void Save(const Napi::CallbackInfo& info);
|
||||
void Restore(const Napi::CallbackInfo& info);
|
||||
void Rotate(const Napi::CallbackInfo& info);
|
||||
void Translate(const Napi::CallbackInfo& info);
|
||||
void Scale(const Napi::CallbackInfo& info);
|
||||
void Transform(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetTransform(const Napi::CallbackInfo& info);
|
||||
void ResetTransform(const Napi::CallbackInfo& info);
|
||||
void SetTransform(const Napi::CallbackInfo& info);
|
||||
Napi::Value IsPointInPath(const Napi::CallbackInfo& info);
|
||||
void BeginPath(const Napi::CallbackInfo& info);
|
||||
void ClosePath(const Napi::CallbackInfo& info);
|
||||
void AddPage(const Napi::CallbackInfo& info);
|
||||
void Clip(const Napi::CallbackInfo& info);
|
||||
void Fill(const Napi::CallbackInfo& info);
|
||||
void Stroke(const Napi::CallbackInfo& info);
|
||||
void FillText(const Napi::CallbackInfo& info);
|
||||
void 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);
|
||||
void SetLineDash(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetLineDash(const Napi::CallbackInfo& info);
|
||||
Napi::Value MeasureText(const Napi::CallbackInfo& info);
|
||||
void BezierCurveTo(const Napi::CallbackInfo& info);
|
||||
void QuadraticCurveTo(const Napi::CallbackInfo& info);
|
||||
void LineTo(const Napi::CallbackInfo& info);
|
||||
void MoveTo(const Napi::CallbackInfo& info);
|
||||
void FillRect(const Napi::CallbackInfo& info);
|
||||
void StrokeRect(const Napi::CallbackInfo& info);
|
||||
void ClearRect(const Napi::CallbackInfo& info);
|
||||
void Rect(const Napi::CallbackInfo& info);
|
||||
void RoundRect(const Napi::CallbackInfo& info);
|
||||
void Arc(const Napi::CallbackInfo& info);
|
||||
void ArcTo(const Napi::CallbackInfo& info);
|
||||
void Ellipse(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetImageData(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreateImageData(const Napi::CallbackInfo& info);
|
||||
static Napi::Value GetStrokeColor(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreatePattern(const Napi::CallbackInfo& info);
|
||||
Napi::Value CreateLinearGradient(const Napi::CallbackInfo& info);
|
||||
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);
|
||||
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 16, 0)
|
||||
void BeginTag(const Napi::CallbackInfo& info);
|
||||
void EndTag(const Napi::CallbackInfo& info);
|
||||
#endif
|
||||
Napi::Value GetDirection(const Napi::CallbackInfo& info);
|
||||
void SetDirection(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; }
|
||||
~Context2d();
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
void _resetPersistentHandles();
|
||||
Napi::Value _getFillColor();
|
||||
Napi::Value _getStrokeColor();
|
||||
Napi::Value get_current_transform();
|
||||
void _setFillColor(Napi::Value arg);
|
||||
void _setFillPattern(Napi::Value arg);
|
||||
void _setStrokeColor(Napi::Value arg);
|
||||
void _setStrokePattern(Napi::Value arg);
|
||||
void checkFonts();
|
||||
void paintText(const Napi::CallbackInfo&, bool);
|
||||
Napi::Reference<Napi::Value> _fillStyle;
|
||||
Napi::Reference<Napi::Value> _strokeStyle;
|
||||
Canvas *_canvas;
|
||||
cairo_t *_context = nullptr;
|
||||
cairo_path_t *_path;
|
||||
PangoLayout *_layout = nullptr;
|
||||
int fontSerial = 1;
|
||||
};
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
// This is used for classifying characters according to the definition of tokens
|
||||
// in the CSS standards, but could be extended for any other future uses
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CharData {
|
||||
static constexpr uint8_t Whitespace = 0x1;
|
||||
static constexpr uint8_t Newline = 0x2;
|
||||
static constexpr uint8_t Hex = 0x4;
|
||||
static constexpr uint8_t Nmstart = 0x8;
|
||||
static constexpr uint8_t Nmchar = 0x10;
|
||||
static constexpr uint8_t Sign = 0x20;
|
||||
static constexpr uint8_t Digit = 0x40;
|
||||
static constexpr uint8_t NumStart = 0x80;
|
||||
};
|
||||
|
||||
using namespace CharData;
|
||||
|
||||
constexpr const uint8_t charData[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-8
|
||||
Whitespace, // 9 (HT)
|
||||
Whitespace | Newline, // 10 (LF)
|
||||
0, // 11 (VT)
|
||||
Whitespace | Newline, // 12 (FF)
|
||||
Whitespace | Newline, // 13 (CR)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14-31
|
||||
Whitespace, // 32 (Space)
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 33-42
|
||||
Sign | NumStart, // 43 (+)
|
||||
0, // 44
|
||||
Nmchar | Sign | NumStart, // 45 (-)
|
||||
0, 0, // 46-47
|
||||
Nmchar | Digit | NumStart | Hex, // 48 (0)
|
||||
Nmchar | Digit | NumStart | Hex, // 49 (1)
|
||||
Nmchar | Digit | NumStart | Hex, // 50 (2)
|
||||
Nmchar | Digit | NumStart | Hex, // 51 (3)
|
||||
Nmchar | Digit | NumStart | Hex, // 52 (4)
|
||||
Nmchar | Digit | NumStart | Hex, // 53 (5)
|
||||
Nmchar | Digit | NumStart | Hex, // 54 (6)
|
||||
Nmchar | Digit | NumStart | Hex, // 55 (7)
|
||||
Nmchar | Digit | NumStart | Hex, // 56 (8)
|
||||
Nmchar | Digit | NumStart | Hex, // 57 (9)
|
||||
0, 0, 0, 0, 0, 0, 0, // 58-64
|
||||
Nmstart | Nmchar | Hex, // 65 (A)
|
||||
Nmstart | Nmchar | Hex, // 66 (B)
|
||||
Nmstart | Nmchar | Hex, // 67 (C)
|
||||
Nmstart | Nmchar | Hex, // 68 (D)
|
||||
Nmstart | Nmchar | Hex, // 69 (E)
|
||||
Nmstart | Nmchar | Hex, // 70 (F)
|
||||
Nmstart | Nmchar, // 71 (G)
|
||||
Nmstart | Nmchar, // 72 (H)
|
||||
Nmstart | Nmchar, // 73 (I)
|
||||
Nmstart | Nmchar, // 74 (J)
|
||||
Nmstart | Nmchar, // 75 (K)
|
||||
Nmstart | Nmchar, // 76 (L)
|
||||
Nmstart | Nmchar, // 77 (M)
|
||||
Nmstart | Nmchar, // 78 (N)
|
||||
Nmstart | Nmchar, // 79 (O)
|
||||
Nmstart | Nmchar, // 80 (P)
|
||||
Nmstart | Nmchar, // 81 (Q)
|
||||
Nmstart | Nmchar, // 82 (R)
|
||||
Nmstart | Nmchar, // 83 (S)
|
||||
Nmstart | Nmchar, // 84 (T)
|
||||
Nmstart | Nmchar, // 85 (U)
|
||||
Nmstart | Nmchar, // 86 (V)
|
||||
Nmstart | Nmchar, // 87 (W)
|
||||
Nmstart | Nmchar, // 88 (X)
|
||||
Nmstart | Nmchar, // 89 (Y)
|
||||
Nmstart | Nmchar, // 90 (Z)
|
||||
0, // 91
|
||||
Nmstart, // 92 (\)
|
||||
0, 0, // 93-94
|
||||
Nmstart | Nmchar, // 95 (_)
|
||||
0, // 96
|
||||
Nmstart | Nmchar | Hex, // 97 (a)
|
||||
Nmstart | Nmchar | Hex, // 98 (b)
|
||||
Nmstart | Nmchar | Hex, // 99 (c)
|
||||
Nmstart | Nmchar | Hex, // 100 (d)
|
||||
Nmstart | Nmchar | Hex, // 101 (e)
|
||||
Nmstart | Nmchar | Hex, // 102 (f)
|
||||
Nmstart | Nmchar, // 103 (g)
|
||||
Nmstart | Nmchar, // 104 (h)
|
||||
Nmstart | Nmchar, // 105 (i)
|
||||
Nmstart | Nmchar, // 106 (j)
|
||||
Nmstart | Nmchar, // 107 (k)
|
||||
Nmstart | Nmchar, // 108 (l)
|
||||
Nmstart | Nmchar, // 109 (m)
|
||||
Nmstart | Nmchar, // 110 (n)
|
||||
Nmstart | Nmchar, // 111 (o)
|
||||
Nmstart | Nmchar, // 112 (p)
|
||||
Nmstart | Nmchar, // 113 (q)
|
||||
Nmstart | Nmchar, // 114 (r)
|
||||
Nmstart | Nmchar, // 115 (s)
|
||||
Nmstart | Nmchar, // 116 (t)
|
||||
Nmstart | Nmchar, // 117 (u)
|
||||
Nmstart | Nmchar, // 118 (v)
|
||||
Nmstart | Nmchar, // 119 (w)
|
||||
Nmstart | Nmchar, // 120 (x)
|
||||
Nmstart | Nmchar, // 121 (y)
|
||||
Nmstart | Nmchar, // 122 (z)
|
||||
0, 0, 0, 0, 0, // 123-127
|
||||
// Non-ASCII
|
||||
Nmstart | Nmchar, // 128
|
||||
Nmstart | Nmchar, // 129
|
||||
Nmstart | Nmchar, // 130
|
||||
Nmstart | Nmchar, // 131
|
||||
Nmstart | Nmchar, // 132
|
||||
Nmstart | Nmchar, // 133
|
||||
Nmstart | Nmchar, // 134
|
||||
Nmstart | Nmchar, // 135
|
||||
Nmstart | Nmchar, // 136
|
||||
Nmstart | Nmchar, // 137
|
||||
Nmstart | Nmchar, // 138
|
||||
Nmstart | Nmchar, // 139
|
||||
Nmstart | Nmchar, // 140
|
||||
Nmstart | Nmchar, // 141
|
||||
Nmstart | Nmchar, // 142
|
||||
Nmstart | Nmchar, // 143
|
||||
Nmstart | Nmchar, // 144
|
||||
Nmstart | Nmchar, // 145
|
||||
Nmstart | Nmchar, // 146
|
||||
Nmstart | Nmchar, // 147
|
||||
Nmstart | Nmchar, // 148
|
||||
Nmstart | Nmchar, // 149
|
||||
Nmstart | Nmchar, // 150
|
||||
Nmstart | Nmchar, // 151
|
||||
Nmstart | Nmchar, // 152
|
||||
Nmstart | Nmchar, // 153
|
||||
Nmstart | Nmchar, // 154
|
||||
Nmstart | Nmchar, // 155
|
||||
Nmstart | Nmchar, // 156
|
||||
Nmstart | Nmchar, // 157
|
||||
Nmstart | Nmchar, // 158
|
||||
Nmstart | Nmchar, // 159
|
||||
Nmstart | Nmchar, // 160
|
||||
Nmstart | Nmchar, // 161
|
||||
Nmstart | Nmchar, // 162
|
||||
Nmstart | Nmchar, // 163
|
||||
Nmstart | Nmchar, // 164
|
||||
Nmstart | Nmchar, // 165
|
||||
Nmstart | Nmchar, // 166
|
||||
Nmstart | Nmchar, // 167
|
||||
Nmstart | Nmchar, // 168
|
||||
Nmstart | Nmchar, // 169
|
||||
Nmstart | Nmchar, // 170
|
||||
Nmstart | Nmchar, // 171
|
||||
Nmstart | Nmchar, // 172
|
||||
Nmstart | Nmchar, // 173
|
||||
Nmstart | Nmchar, // 174
|
||||
Nmstart | Nmchar, // 175
|
||||
Nmstart | Nmchar, // 176
|
||||
Nmstart | Nmchar, // 177
|
||||
Nmstart | Nmchar, // 178
|
||||
Nmstart | Nmchar, // 179
|
||||
Nmstart | Nmchar, // 180
|
||||
Nmstart | Nmchar, // 181
|
||||
Nmstart | Nmchar, // 182
|
||||
Nmstart | Nmchar, // 183
|
||||
Nmstart | Nmchar, // 184
|
||||
Nmstart | Nmchar, // 185
|
||||
Nmstart | Nmchar, // 186
|
||||
Nmstart | Nmchar, // 187
|
||||
Nmstart | Nmchar, // 188
|
||||
Nmstart | Nmchar, // 189
|
||||
Nmstart | Nmchar, // 190
|
||||
Nmstart | Nmchar, // 191
|
||||
Nmstart | Nmchar, // 192
|
||||
Nmstart | Nmchar, // 193
|
||||
Nmstart | Nmchar, // 194
|
||||
Nmstart | Nmchar, // 195
|
||||
Nmstart | Nmchar, // 196
|
||||
Nmstart | Nmchar, // 197
|
||||
Nmstart | Nmchar, // 198
|
||||
Nmstart | Nmchar, // 199
|
||||
Nmstart | Nmchar, // 200
|
||||
Nmstart | Nmchar, // 201
|
||||
Nmstart | Nmchar, // 202
|
||||
Nmstart | Nmchar, // 203
|
||||
Nmstart | Nmchar, // 204
|
||||
Nmstart | Nmchar, // 205
|
||||
Nmstart | Nmchar, // 206
|
||||
Nmstart | Nmchar, // 207
|
||||
Nmstart | Nmchar, // 208
|
||||
Nmstart | Nmchar, // 209
|
||||
Nmstart | Nmchar, // 210
|
||||
Nmstart | Nmchar, // 211
|
||||
Nmstart | Nmchar, // 212
|
||||
Nmstart | Nmchar, // 213
|
||||
Nmstart | Nmchar, // 214
|
||||
Nmstart | Nmchar, // 215
|
||||
Nmstart | Nmchar, // 216
|
||||
Nmstart | Nmchar, // 217
|
||||
Nmstart | Nmchar, // 218
|
||||
Nmstart | Nmchar, // 219
|
||||
Nmstart | Nmchar, // 220
|
||||
Nmstart | Nmchar, // 221
|
||||
Nmstart | Nmchar, // 222
|
||||
Nmstart | Nmchar, // 223
|
||||
Nmstart | Nmchar, // 224
|
||||
Nmstart | Nmchar, // 225
|
||||
Nmstart | Nmchar, // 226
|
||||
Nmstart | Nmchar, // 227
|
||||
Nmstart | Nmchar, // 228
|
||||
Nmstart | Nmchar, // 229
|
||||
Nmstart | Nmchar, // 230
|
||||
Nmstart | Nmchar, // 231
|
||||
Nmstart | Nmchar, // 232
|
||||
Nmstart | Nmchar, // 233
|
||||
Nmstart | Nmchar, // 234
|
||||
Nmstart | Nmchar, // 235
|
||||
Nmstart | Nmchar, // 236
|
||||
Nmstart | Nmchar, // 237
|
||||
Nmstart | Nmchar, // 238
|
||||
Nmstart | Nmchar, // 239
|
||||
Nmstart | Nmchar, // 240
|
||||
Nmstart | Nmchar, // 241
|
||||
Nmstart | Nmchar, // 242
|
||||
Nmstart | Nmchar, // 243
|
||||
Nmstart | Nmchar, // 244
|
||||
Nmstart | Nmchar, // 245
|
||||
Nmstart | Nmchar, // 246
|
||||
Nmstart | Nmchar, // 247
|
||||
Nmstart | Nmchar, // 248
|
||||
Nmstart | Nmchar, // 249
|
||||
Nmstart | Nmchar, // 250
|
||||
Nmstart | Nmchar, // 251
|
||||
Nmstart | Nmchar, // 252
|
||||
Nmstart | Nmchar, // 253
|
||||
Nmstart | Nmchar, // 254
|
||||
Nmstart | Nmchar // 255
|
||||
};
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
// This is written to exactly parse the `font` shorthand in CSS2:
|
||||
// https://www.w3.org/TR/CSS22/fonts.html#font-shorthand
|
||||
// https://www.w3.org/TR/CSS22/syndata.html#tokenization
|
||||
//
|
||||
// We may want to update it for CSS 3 (e.g. font-stretch, or updated
|
||||
// tokenization) but I've only ever seen one or two issues filed in node-canvas
|
||||
// due to parsing in my 8 years on the project
|
||||
|
||||
#include "FontParser.h"
|
||||
#include "CharData.h"
|
||||
#include <cctype>
|
||||
#include <unordered_map>
|
||||
|
||||
Token::Token(Type type, std::string value) : type_(type), value_(std::move(value)) {}
|
||||
|
||||
Token::Token(Type type, double value) : type_(type), value_(value) {}
|
||||
|
||||
Token::Token(Type type) : type_(type), value_(std::string{}) {}
|
||||
|
||||
const std::string&
|
||||
Token::getString() const {
|
||||
static const std::string empty;
|
||||
auto* str = std::get_if<std::string>(&value_);
|
||||
return str ? *str : empty;
|
||||
}
|
||||
|
||||
double
|
||||
Token::getNumber() const {
|
||||
auto* num = std::get_if<double>(&value_);
|
||||
return num ? *num : 0.0f;
|
||||
}
|
||||
|
||||
Tokenizer::Tokenizer(std::string_view input) : input_(input) {}
|
||||
|
||||
std::string
|
||||
Tokenizer::utf8Encode(uint32_t codepoint) {
|
||||
std::string result;
|
||||
|
||||
if (codepoint < 0x80) {
|
||||
result += static_cast<char>(codepoint);
|
||||
} else if (codepoint < 0x800) {
|
||||
result += static_cast<char>((codepoint >> 6) | 0xc0);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
} else if (codepoint < 0x10000) {
|
||||
result += static_cast<char>((codepoint >> 12) | 0xe0);
|
||||
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
} else {
|
||||
result += static_cast<char>((codepoint >> 18) | 0xf0);
|
||||
result += static_cast<char>(((codepoint >> 12) & 0x3f) | 0x80);
|
||||
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
||||
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char
|
||||
Tokenizer::peek() const {
|
||||
return position_ < input_.length() ? input_[position_] : '\0';
|
||||
}
|
||||
|
||||
char
|
||||
Tokenizer::advance() {
|
||||
return position_ < input_.length() ? input_[position_++] : '\0';
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::parseNumber() {
|
||||
enum class State {
|
||||
Start,
|
||||
AfterSign,
|
||||
Digits,
|
||||
AfterDecimal,
|
||||
AfterE,
|
||||
AfterESign,
|
||||
ExponentDigits
|
||||
};
|
||||
|
||||
size_t start = position_;
|
||||
size_t ePosition = 0;
|
||||
State state = State::Start;
|
||||
bool valid = false;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
uint8_t flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
switch (state) {
|
||||
case State::Start:
|
||||
if (flags & CharData::Sign) {
|
||||
position_++;
|
||||
state = State::AfterSign;
|
||||
} else if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
state = State::Digits;
|
||||
valid = true;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterSign:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
state = State::Digits;
|
||||
valid = true;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::Digits:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
} else if (c == '.') {
|
||||
position_++;
|
||||
state = State::AfterDecimal;
|
||||
} else if (c == 'e' || c == 'E') {
|
||||
ePosition = position_;
|
||||
position_++;
|
||||
state = State::AfterE;
|
||||
valid = false;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterDecimal:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::Digits;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterE:
|
||||
if (flags & CharData::Sign) {
|
||||
position_++;
|
||||
state = State::AfterESign;
|
||||
} else if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::ExponentDigits;
|
||||
} else {
|
||||
position_ = ePosition;
|
||||
valid = true;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::AfterESign:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
valid = true;
|
||||
state = State::ExponentDigits;
|
||||
} else {
|
||||
position_ = ePosition;
|
||||
valid = true;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::ExponentDigits:
|
||||
if (flags & CharData::Digit) {
|
||||
position_++;
|
||||
} else {
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (!valid) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
|
||||
std::string number_str(input_.substr(start, position_ - start));
|
||||
double value = std::stod(number_str);
|
||||
return Token(Token::Type::Number, value);
|
||||
}
|
||||
|
||||
// Note that identifiers are always lower-case. This helps us make easier/more
|
||||
// efficient comparisons, but means that font-families specified as identifiers
|
||||
// will be lower-cased. Since font selection isn't case sensitive, this
|
||||
// shouldn't ever be a problem.
|
||||
Token
|
||||
Tokenizer::parseIdentifier() {
|
||||
std::string identifier;
|
||||
auto flags = CharData::Nmstart;
|
||||
auto start = position_;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
|
||||
if (c == '\\') {
|
||||
advance();
|
||||
if (!parseEscape(identifier)) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
flags = CharData::Nmchar;
|
||||
} else if (charData[static_cast<uint8_t>(c)] & flags) {
|
||||
identifier += advance() + (c >= 'A' && c <= 'Z' ? 32 : 0);
|
||||
flags = CharData::Nmchar;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Token(Token::Type::Identifier, identifier);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Tokenizer::parseUnicode() {
|
||||
uint32_t value = 0;
|
||||
size_t count = 0;
|
||||
|
||||
while (position_ < input_.length() && count < 6) {
|
||||
char c = peek();
|
||||
uint32_t digit;
|
||||
|
||||
if (c >= '0' && c <= '9') {
|
||||
digit = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
digit = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
digit = c - 'A' + 10;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
value = value * 16 + digit;
|
||||
advance();
|
||||
count++;
|
||||
}
|
||||
|
||||
// Optional whitespace after hex escape
|
||||
char c = peek();
|
||||
if (c == '\r') {
|
||||
advance();
|
||||
if (peek() == '\n') advance();
|
||||
} else if (isWhitespace(c)) {
|
||||
advance();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool
|
||||
Tokenizer::parseEscape(std::string& str) {
|
||||
char c = peek();
|
||||
auto flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
if (flags & CharData::Hex) {
|
||||
str += utf8Encode(parseUnicode());
|
||||
return true;
|
||||
} else if (!(flags & CharData::Newline) && !(flags & CharData::Hex)) {
|
||||
str += advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::parseString(char quote) {
|
||||
advance();
|
||||
std::string value;
|
||||
auto start = position_;
|
||||
|
||||
while (position_ < input_.length()) {
|
||||
char c = peek();
|
||||
|
||||
if (c == quote) {
|
||||
advance();
|
||||
return Token(Token::Type::QuotedString, value);
|
||||
} else if (c == '\\') {
|
||||
advance();
|
||||
c = peek();
|
||||
if (c == '\r') {
|
||||
advance();
|
||||
if (peek() == '\n') advance();
|
||||
} else if (isNewline(c)) {
|
||||
advance();
|
||||
} else {
|
||||
if (!parseEscape(value)) {
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value += advance();
|
||||
}
|
||||
}
|
||||
|
||||
position_ = start;
|
||||
return Token(Token::Type::Invalid);
|
||||
}
|
||||
|
||||
Token
|
||||
Tokenizer::nextToken() {
|
||||
if (position_ >= input_.length()) {
|
||||
return Token(Token::Type::EndOfInput);
|
||||
}
|
||||
|
||||
char c = peek();
|
||||
auto flags = charData[static_cast<uint8_t>(c)];
|
||||
|
||||
if (isWhitespace(c)) {
|
||||
std::string whitespace;
|
||||
while (position_ < input_.length() && isWhitespace(peek())) {
|
||||
whitespace += advance();
|
||||
}
|
||||
return Token(Token::Type::Whitespace, whitespace);
|
||||
}
|
||||
|
||||
if (flags & CharData::NumStart) {
|
||||
Token token = parseNumber();
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (flags & CharData::Nmstart) {
|
||||
Token token = parseIdentifier();
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
Token token = parseString('"');
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
if (c == '\'') {
|
||||
Token token = parseString('\'');
|
||||
if (token.type() != Token::Type::Invalid) return token;
|
||||
}
|
||||
|
||||
switch (advance()) {
|
||||
case '/': return Token(Token::Type::Slash);
|
||||
case ',': return Token(Token::Type::Comma);
|
||||
case '%': return Token(Token::Type::Percent);
|
||||
default: return Token(Token::Type::Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
FontParser::FontParser(std::string_view input)
|
||||
: tokenizer_(input)
|
||||
, currentToken_(tokenizer_.nextToken())
|
||||
, nextToken_(tokenizer_.nextToken()) {}
|
||||
|
||||
const std::unordered_map<std::string, uint16_t> FontParser::weightMap = {
|
||||
{"normal", 400},
|
||||
{"bold", 700},
|
||||
{"lighter", 100},
|
||||
{"bolder", 700}
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, double> FontParser::unitMap = {
|
||||
{"cm", 37.8f},
|
||||
{"mm", 3.78f},
|
||||
{"in", 96.0f},
|
||||
{"pt", 96.0f / 72.0f},
|
||||
{"pc", 96.0f / 6.0f},
|
||||
{"em", 16.0f},
|
||||
{"px", 1.0f}
|
||||
};
|
||||
|
||||
void
|
||||
FontParser::advance() {
|
||||
currentToken_ = nextToken_;
|
||||
nextToken_ = tokenizer_.nextToken();
|
||||
}
|
||||
|
||||
void
|
||||
FontParser::skipWs() {
|
||||
while (currentToken_.type() == Token::Type::Whitespace) advance();
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::check(Token::Type type) const {
|
||||
return currentToken_.type() == type;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::checkWs() const {
|
||||
return nextToken_.type() == Token::Type::Whitespace
|
||||
|| nextToken_.type() == Token::Type::EndOfInput;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontStyle(FontProperties& props) {
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
if (value == "italic") {
|
||||
props.fontStyle = FontStyle::Italic;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "oblique") {
|
||||
props.fontStyle = FontStyle::Oblique;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "normal") {
|
||||
props.fontStyle = FontStyle::Normal;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontVariant(FontProperties& props) {
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
if (value == "small-caps") {
|
||||
props.fontVariant = FontVariant::SmallCaps;
|
||||
advance();
|
||||
return true;
|
||||
} else if (value == "normal") {
|
||||
props.fontVariant = FontVariant::Normal;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontWeight(FontProperties& props) {
|
||||
if (check(Token::Type::Number)) {
|
||||
double weightFloat = currentToken_.getNumber();
|
||||
int weight = static_cast<int>(weightFloat);
|
||||
if (weight < 1 || weight > 1000) return false;
|
||||
props.fontWeight = static_cast<uint16_t>(weight);
|
||||
advance();
|
||||
return true;
|
||||
} else if (check(Token::Type::Identifier)) {
|
||||
const auto& value = currentToken_.getString();
|
||||
|
||||
if (auto it = weightMap.find(value); it != weightMap.end()) {
|
||||
props.fontWeight = it->second;
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontSize(FontProperties& props) {
|
||||
if (!check(Token::Type::Number)) return false;
|
||||
|
||||
props.fontSize = currentToken_.getNumber();
|
||||
advance();
|
||||
|
||||
double multiplier = 1.0f;
|
||||
if (check(Token::Type::Identifier)) {
|
||||
const auto& unit = currentToken_.getString();
|
||||
|
||||
if (auto it = unitMap.find(unit); it != unitMap.end()) {
|
||||
multiplier = it->second;
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (check(Token::Type::Percent)) {
|
||||
multiplier = 16.0f / 100.0f;
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Technically if we consumed some tokens but couldn't parse the font-size,
|
||||
// we should rewind the tokenizer, but I don't think the grammar allows for
|
||||
// any valid alternates in this specific case
|
||||
|
||||
props.fontSize *= multiplier;
|
||||
return true;
|
||||
}
|
||||
|
||||
// line-height is not used by canvas ever, but should still parse
|
||||
bool
|
||||
FontParser::parseLineHeight(FontProperties& props) {
|
||||
if (check(Token::Type::Slash)) {
|
||||
advance();
|
||||
skipWs();
|
||||
if (check(Token::Type::Number)) {
|
||||
advance();
|
||||
if (check(Token::Type::Percent)) {
|
||||
advance();
|
||||
} else if (check(Token::Type::Identifier)) {
|
||||
auto identifier = currentToken_.getString();
|
||||
if (auto it = unitMap.find(identifier); it != unitMap.end()) {
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (check(Token::Type::Identifier) && currentToken_.getString() == "normal") {
|
||||
advance();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
FontParser::parseFontFamily(FontProperties& props) {
|
||||
while (!check(Token::Type::EndOfInput)) {
|
||||
std::string family = "";
|
||||
std::string trailingWs = "";
|
||||
bool found = false;
|
||||
|
||||
while (
|
||||
check(Token::Type::QuotedString) ||
|
||||
check(Token::Type::Identifier) ||
|
||||
check(Token::Type::Whitespace)
|
||||
) {
|
||||
if (check(Token::Type::Whitespace)) {
|
||||
if (found) trailingWs += currentToken_.getString();
|
||||
} else { // Identifier, QuotedString
|
||||
if (found) {
|
||||
family += trailingWs;
|
||||
trailingWs.clear();
|
||||
}
|
||||
|
||||
family += currentToken_.getString();
|
||||
found = true;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
|
||||
if (!found) return false; // only whitespace or non-id/string found
|
||||
|
||||
props.fontFamily.push_back(family);
|
||||
|
||||
if (check(Token::Type::Comma)) advance();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FontProperties
|
||||
FontParser::parse(const std::string& fontString, bool* success) {
|
||||
FontParser parser(fontString);
|
||||
auto result = parser.parseFont();
|
||||
if (success) *success = !parser.hasError_;
|
||||
return result;
|
||||
}
|
||||
|
||||
FontProperties
|
||||
FontParser::parseFont() {
|
||||
FontProperties props;
|
||||
uint8_t state = 0b111;
|
||||
|
||||
skipWs();
|
||||
|
||||
for (size_t i = 0; i < 3 && checkWs(); i++) {
|
||||
if ((state & 0b001) && parseFontStyle(props)) {
|
||||
state &= 0b110;
|
||||
goto match;
|
||||
}
|
||||
|
||||
if ((state & 0b010) && parseFontVariant(props)) {
|
||||
state &= 0b101;
|
||||
goto match;
|
||||
}
|
||||
|
||||
if ((state & 0b100) && parseFontWeight(props)) {
|
||||
state &= 0b011;
|
||||
goto match;
|
||||
}
|
||||
|
||||
break; // all attempts exhausted
|
||||
match: skipWs(); // success: move to the next non-ws token
|
||||
}
|
||||
|
||||
if (parseFontSize(props)) {
|
||||
skipWs();
|
||||
if (parseLineHeight(props) && parseFontFamily(props)) {
|
||||
return props;
|
||||
}
|
||||
}
|
||||
|
||||
hasError_ = true;
|
||||
return props;
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include "CharData.h"
|
||||
|
||||
enum class FontStyle {
|
||||
Normal,
|
||||
Italic,
|
||||
Oblique
|
||||
};
|
||||
|
||||
enum class FontVariant {
|
||||
Normal,
|
||||
SmallCaps
|
||||
};
|
||||
|
||||
struct FontProperties {
|
||||
double fontSize{16.0f};
|
||||
std::vector<std::string> fontFamily;
|
||||
uint16_t fontWeight{400};
|
||||
FontVariant fontVariant{FontVariant::Normal};
|
||||
FontStyle fontStyle{FontStyle::Normal};
|
||||
};
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum class Type {
|
||||
Invalid,
|
||||
Number,
|
||||
Percent,
|
||||
Identifier,
|
||||
Slash,
|
||||
Comma,
|
||||
QuotedString,
|
||||
Whitespace,
|
||||
EndOfInput
|
||||
};
|
||||
|
||||
Token(Type type, std::string value);
|
||||
Token(Type type, double value);
|
||||
Token(Type type);
|
||||
|
||||
Type type() const { return type_; }
|
||||
|
||||
const std::string& getString() const;
|
||||
double getNumber() const;
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
std::variant<std::string, double> value_;
|
||||
};
|
||||
|
||||
class Tokenizer {
|
||||
public:
|
||||
Tokenizer(std::string_view input);
|
||||
Token nextToken();
|
||||
|
||||
private:
|
||||
std::string_view input_;
|
||||
size_t position_{0};
|
||||
|
||||
// Util
|
||||
std::string utf8Encode(uint32_t codepoint);
|
||||
inline bool isWhitespace(char c) const {
|
||||
return charData[static_cast<uint8_t>(c)] & CharData::Whitespace;
|
||||
}
|
||||
inline bool isNewline(char c) const {
|
||||
return charData[static_cast<uint8_t>(c)] & CharData::Newline;
|
||||
}
|
||||
|
||||
// Moving through the string
|
||||
char peek() const;
|
||||
char advance();
|
||||
|
||||
// Tokenize
|
||||
Token parseNumber();
|
||||
Token parseIdentifier();
|
||||
uint32_t parseUnicode();
|
||||
bool parseEscape(std::string& str);
|
||||
Token parseString(char quote);
|
||||
};
|
||||
|
||||
class FontParser {
|
||||
public:
|
||||
static FontProperties parse(const std::string& fontString, bool* success = nullptr);
|
||||
|
||||
private:
|
||||
static const std::unordered_map<std::string, uint16_t> weightMap;
|
||||
static const std::unordered_map<std::string, double> unitMap;
|
||||
|
||||
FontParser(std::string_view input);
|
||||
|
||||
void advance();
|
||||
void skipWs();
|
||||
bool check(Token::Type type) const;
|
||||
bool checkWs() const;
|
||||
|
||||
bool parseFontStyle(FontProperties& props);
|
||||
bool parseFontVariant(FontProperties& props);
|
||||
bool parseFontWeight(FontProperties& props);
|
||||
bool parseFontSize(FontProperties& props);
|
||||
bool parseLineHeight(FontProperties& props);
|
||||
bool parseFontFamily(FontProperties& props);
|
||||
FontProperties parseFont();
|
||||
|
||||
Tokenizer tokenizer_;
|
||||
Token currentToken_;
|
||||
Token nextToken_;
|
||||
bool hasError_{false};
|
||||
};
|
||||
+1727
File diff suppressed because it is too large.
Load diff
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include "CanvasError.h"
|
||||
#include <functional>
|
||||
#include <napi.h>
|
||||
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
|
||||
|
||||
#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 Napi::ObjectWrap<Image> {
|
||||
public:
|
||||
char *filename;
|
||||
int width, height;
|
||||
int naturalWidth, naturalHeight;
|
||||
Napi::Env env;
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Env& env, Napi::Object& target);
|
||||
Image(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetComplete(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetNaturalWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetNaturalHeight(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetDataMode(const Napi::CallbackInfo& info);
|
||||
void SetDataMode(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetWidth(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
void SetHeight(const Napi::CallbackInfo& info, const Napi::Value& value);
|
||||
static Napi::Value GetSource(const Napi::CallbackInfo& info);
|
||||
static void SetSource(const Napi::CallbackInfo& info);
|
||||
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
|
||||
enum Orientation {
|
||||
NORMAL,
|
||||
MIRROR_HORIZ,
|
||||
MIRROR_VERT,
|
||||
ROTATE_180,
|
||||
ROTATE_90_CW,
|
||||
ROTATE_270_CW,
|
||||
MIRROR_HORIZ_AND_ROTATE_90_CW,
|
||||
MIRROR_HORIZ_AND_ROTATE_270_CW
|
||||
};
|
||||
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, Orientation orientation);
|
||||
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
|
||||
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
|
||||
|
||||
class Reader {
|
||||
public:
|
||||
virtual bool hasBytes(unsigned n) const = 0;
|
||||
virtual uint8_t getNext() = 0;
|
||||
virtual void skipBytes(unsigned n) = 0;
|
||||
};
|
||||
Orientation getExifOrientation(Reader& jpeg);
|
||||
void updateDimensionsForOrientation(Orientation orientation);
|
||||
void rotatePixels(uint8_t* pixels, int width, int height, int channels, Orientation orientation);
|
||||
#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
|
||||
};
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#include "ImageData.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
/*
|
||||
* Initialize ImageData.
|
||||
*/
|
||||
|
||||
void
|
||||
ImageData::Initialize(Napi::Env& env, Napi::Object& exports) {
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
InstanceData *data = env.GetInstanceData<InstanceData>();
|
||||
|
||||
Napi::Function ctor = DefineClass(env, "ImageData", {
|
||||
InstanceAccessor<&ImageData::GetWidth>("width", napi_default_jsproperty),
|
||||
InstanceAccessor<&ImageData::GetHeight>("height", napi_default_jsproperty)
|
||||
});
|
||||
|
||||
exports.Set("ImageData", ctor);
|
||||
data->ImageDataCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new ImageData object.
|
||||
*/
|
||||
|
||||
ImageData::ImageData(const Napi::CallbackInfo& info) : Napi::ObjectWrap<ImageData>(info), env(info.Env()) {
|
||||
Napi::TypedArray dataArray;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int length;
|
||||
|
||||
if (info[0].IsNumber() && info[1].IsNumber()) {
|
||||
width = info[0].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
height = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (height == 0) {
|
||||
Napi::RangeError::New(env, "The source height is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented.
|
||||
|
||||
dataArray = Napi::Uint8Array::New(env, length, napi_uint8_clamped_array);
|
||||
} else if (
|
||||
info[0].IsTypedArray() &&
|
||||
info[0].As<Napi::TypedArray>().TypedArrayType() == napi_uint8_clamped_array &&
|
||||
info[1].IsNumber()
|
||||
) {
|
||||
dataArray = info[0].As<Napi::Uint8Array>();
|
||||
|
||||
length = dataArray.ElementLength();
|
||||
if (length == 0) {
|
||||
Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't assert that the ImageData length is a multiple of four because some
|
||||
// data formats are not 4 BPP.
|
||||
|
||||
width = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't assert that the byte length is a multiple of 4 * width, ditto.
|
||||
|
||||
if (info[2].IsNumber()) { // Explicit height given
|
||||
height = info[2].As<Napi::Number>().Uint32Value();
|
||||
} else { // Calculate height assuming 4 BPP
|
||||
int size = length / 4;
|
||||
height = size / width;
|
||||
}
|
||||
} else if (
|
||||
info[0].IsTypedArray() &&
|
||||
info[0].As<Napi::TypedArray>().TypedArrayType() == napi_uint16_array &&
|
||||
info[1].IsNumber()
|
||||
) { // Intended for RGB16_565 format
|
||||
dataArray = info[0].As<Napi::TypedArray>();
|
||||
|
||||
length = dataArray.ElementLength();
|
||||
if (length == 0) {
|
||||
Napi::RangeError::New(env, "The input data has a zero byte length.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
width = info[1].As<Napi::Number>().Uint32Value();
|
||||
if (width == 0) {
|
||||
Napi::RangeError::New(env, "The source width is zero.").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
if (info[2].IsNumber()) { // Explicit height given
|
||||
height = info[2].As<Napi::Number>().Uint32Value();
|
||||
} else { // Calculate height assuming 2 BPP
|
||||
int size = length / 2;
|
||||
height = size / width;
|
||||
}
|
||||
} else {
|
||||
Napi::TypeError::New(env, "Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)").ThrowAsJavaScriptException();
|
||||
return;
|
||||
}
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
_data = dataArray.As<Napi::Uint8Array>().Data();
|
||||
|
||||
info.This().As<Napi::Object>().Set("data", dataArray);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
ImageData::GetWidth(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, width());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
Napi::Value
|
||||
ImageData::GetHeight(const Napi::CallbackInfo& info) {
|
||||
return Napi::Number::New(env, height());
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <napi.h>
|
||||
#include <stdint.h> // node < 7 uses libstdc++ on macOS which lacks complete c++11
|
||||
|
||||
class ImageData : public Napi::ObjectWrap<ImageData> {
|
||||
public:
|
||||
static void Initialize(Napi::Env& env, Napi::Object& exports);
|
||||
ImageData(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetWidth(const Napi::CallbackInfo& info);
|
||||
Napi::Value GetHeight(const Napi::CallbackInfo& info);
|
||||
|
||||
inline int width() { return _width; }
|
||||
inline int height() { return _height; }
|
||||
inline uint8_t *data() { return _data; }
|
||||
|
||||
Napi::Env env;
|
||||
|
||||
private:
|
||||
int _width;
|
||||
int _height;
|
||||
uint8_t *_data;
|
||||
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#include <napi.h>
|
||||
|
||||
struct InstanceData {
|
||||
Napi::FunctionReference ImageBackendCtor;
|
||||
Napi::FunctionReference PdfBackendCtor;
|
||||
Napi::FunctionReference SvgBackendCtor;
|
||||
Napi::FunctionReference CanvasCtor;
|
||||
Napi::FunctionReference CanvasGradientCtor;
|
||||
Napi::FunctionReference DOMMatrixCtor;
|
||||
Napi::FunctionReference ImageCtor;
|
||||
Napi::FunctionReference parseFont;
|
||||
Napi::FunctionReference Context2dCtor;
|
||||
Napi::FunctionReference ImageDataCtor;
|
||||
Napi::FunctionReference CanvasPatternCtor;
|
||||
};
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
#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){
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
Napi::Env env = dest->closure->canvas->Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:empty_closure_output_buffer");
|
||||
|
||||
Napi::Object buf = Napi::Buffer<char>::New(env, (char *)dest->buffer, dest->bufsize);
|
||||
|
||||
// emit "data"
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, 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){
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
Napi::Env env = dest->closure->canvas->Env();
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::AsyncContext async(env, "canvas:term_closure_destination");
|
||||
|
||||
/* emit remaining data */
|
||||
Napi::Object buf = Napi::Buffer<char>::New(env, (char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer);
|
||||
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), buf}, async);
|
||||
|
||||
// emit "end"
|
||||
dest->closure->cb.MakeCallback(env.Global(), {env.Null(), env.Null()}, 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);
|
||||
}
|
||||
+292
@@ -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);
|
||||
}
|
||||
+11
@@ -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;
|
||||
};
|
||||
+9
@@ -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);
|
||||
});
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#include "Backend.h"
|
||||
#include <string>
|
||||
#include <napi.h>
|
||||
|
||||
Backend::Backend(std::string name, Napi::CallbackInfo& info) : name(name), env(info.Env()) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if (info[0].IsNumber()) width = info[0].As<Napi::Number>().Int32Value();
|
||||
if (info[1].IsNumber()) height = info[1].As<Napi::Number>().Int32Value();
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
}
|
||||
|
||||
Backend::~Backend()
|
||||
{
|
||||
Backend::destroySurface();
|
||||
}
|
||||
|
||||
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)
|
||||
: 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();
|
||||
};
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include "../dll_visibility.h"
|
||||
#include <exception>
|
||||
#include <napi.h>
|
||||
#include <string>
|
||||
|
||||
class Canvas;
|
||||
|
||||
class Backend
|
||||
{
|
||||
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, Napi::CallbackInfo& info);
|
||||
|
||||
public:
|
||||
Napi::Env env;
|
||||
|
||||
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:
|
||||
std::string operation_name;
|
||||
std::string msg;
|
||||
|
||||
public:
|
||||
BackendOperationNotAvailable(Backend* backend, std::string operation_name);
|
||||
~BackendOperationNotAvailable() throw();
|
||||
|
||||
const char* what() const throw();
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#include "ImageBackend.h"
|
||||
#include "../InstanceData.h"
|
||||
#include <napi.h>
|
||||
#include <cassert>
|
||||
|
||||
ImageBackend::ImageBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<ImageBackend>(info), Backend("image", info)
|
||||
{
|
||||
}
|
||||
|
||||
// This returns an approximate value only, suitable for
|
||||
// Napi::MemoryManagement:: 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);
|
||||
Napi::MemoryManagement::AdjustExternalMemory(env, approxBytesPerPixel() * width * height);
|
||||
return surface;
|
||||
}
|
||||
|
||||
void ImageBackend::destroySurface() {
|
||||
if (surface) {
|
||||
cairo_surface_destroy(surface);
|
||||
surface = nullptr;
|
||||
Napi::MemoryManagement::AdjustExternalMemory(env, -approxBytesPerPixel() * width * height);
|
||||
}
|
||||
}
|
||||
|
||||
cairo_format_t ImageBackend::getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
void ImageBackend::setFormat(cairo_format_t _format) {
|
||||
this->format = _format;
|
||||
}
|
||||
|
||||
Napi::FunctionReference ImageBackend::constructor;
|
||||
|
||||
void ImageBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
Napi::Function ctor = DefineClass(env, "ImageBackend", {});
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
data->ImageBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include <napi.h>
|
||||
|
||||
class ImageBackend : public Napi::ObjectWrap<ImageBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
void destroySurface();
|
||||
cairo_format_t format = DEFAULT_FORMAT;
|
||||
|
||||
public:
|
||||
ImageBackend(Napi::CallbackInfo& info);
|
||||
|
||||
cairo_format_t getFormat();
|
||||
void setFormat(cairo_format_t format);
|
||||
|
||||
int32_t approxBytesPerPixel();
|
||||
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Object target);
|
||||
const static cairo_format_t DEFAULT_FORMAT = CAIRO_FORMAT_ARGB32;
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#include "PdfBackend.h"
|
||||
|
||||
#include <cairo-pdf.h>
|
||||
#include "../InstanceData.h"
|
||||
#include "../Canvas.h"
|
||||
#include "../closure.h"
|
||||
|
||||
PdfBackend::PdfBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<PdfBackend>(info), Backend("pdf", info) {
|
||||
PdfBackend::createSurface();
|
||||
}
|
||||
|
||||
PdfBackend::~PdfBackend() {
|
||||
cairo_surface_finish(surface);
|
||||
if (_closure) delete _closure;
|
||||
destroySurface();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void
|
||||
PdfBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
Napi::Function ctor = DefineClass(env, "PdfBackend", {});
|
||||
data->PdfBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include "../closure.h"
|
||||
#include <napi.h>
|
||||
|
||||
class PdfBackend : public Napi::ObjectWrap<PdfBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
cairo_surface_t* recreateSurface();
|
||||
|
||||
public:
|
||||
PdfSvgClosure* _closure = NULL;
|
||||
inline PdfSvgClosure* closure() { return _closure; }
|
||||
|
||||
PdfBackend(Napi::CallbackInfo& info);
|
||||
~PdfBackend();
|
||||
|
||||
static Napi::FunctionReference constructor;
|
||||
static void Initialize(Napi::Object target);
|
||||
static Napi::Value New(const Napi::CallbackInfo& info);
|
||||
};
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#include "SvgBackend.h"
|
||||
|
||||
#include <cairo-svg.h>
|
||||
#include <napi.h>
|
||||
#include "../Canvas.h"
|
||||
#include "../closure.h"
|
||||
#include "../InstanceData.h"
|
||||
#include <cassert>
|
||||
|
||||
using namespace Napi;
|
||||
|
||||
SvgBackend::SvgBackend(Napi::CallbackInfo& info) : Napi::ObjectWrap<SvgBackend>(info), Backend("svg", info) {
|
||||
SvgBackend::createSurface();
|
||||
}
|
||||
|
||||
SvgBackend::~SvgBackend() {
|
||||
cairo_surface_finish(surface);
|
||||
if (_closure) {
|
||||
delete _closure;
|
||||
_closure = nullptr;
|
||||
}
|
||||
destroySurface();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SvgBackend::Initialize(Napi::Object target) {
|
||||
Napi::Env env = target.Env();
|
||||
Napi::Function ctor = DefineClass(env, "SvgBackend", {});
|
||||
InstanceData* data = env.GetInstanceData<InstanceData>();
|
||||
data->SvgBackendCtor = Napi::Persistent(ctor);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "Backend.h"
|
||||
#include "../closure.h"
|
||||
#include <napi.h>
|
||||
|
||||
class SvgBackend : public Napi::ObjectWrap<SvgBackend>, public Backend
|
||||
{
|
||||
private:
|
||||
cairo_surface_t* createSurface();
|
||||
cairo_surface_t* recreateSurface();
|
||||
|
||||
public:
|
||||
PdfSvgClosure* _closure = NULL;
|
||||
inline PdfSvgClosure* closure() { return _closure; }
|
||||
|
||||
SvgBackend(Napi::CallbackInfo& info);
|
||||
~SvgBackend();
|
||||
|
||||
static void Initialize(Napi::Object target);
|
||||
};
|
||||
+457
@@ -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;
|
||||
}
|
||||
+60
@@ -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
|
||||
+24
@@ -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>
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#include "closure.h"
|
||||
#include "Canvas.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
|
||||
|
||||
void
|
||||
EncodingWorker::Init(void (*work_fn)(Closure*), Closure* closure) {
|
||||
this->work_fn = work_fn;
|
||||
this->closure = closure;
|
||||
}
|
||||
|
||||
void
|
||||
EncodingWorker::Execute() {
|
||||
this->work_fn(this->closure);
|
||||
}
|
||||
|
||||
void
|
||||
EncodingWorker::OnWorkComplete(Napi::Env env, napi_status status) {
|
||||
Napi::HandleScope scope(env);
|
||||
|
||||
if (closure->status) {
|
||||
closure->cb.Call({ closure->canvas->CairoError(closure->status).Value() });
|
||||
} else {
|
||||
Napi::Object buf = Napi::Buffer<uint8_t>::Copy(env, &closure->vec[0], closure->vec.size());
|
||||
closure->cb.Call({ env.Null(), buf });
|
||||
}
|
||||
|
||||
closure->canvas->Unref();
|
||||
delete closure;
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Canvas.h"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
#include <jpeglib.h>
|
||||
#endif
|
||||
|
||||
#include <napi.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;
|
||||
Napi::FunctionReference 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
|
||||
|
||||
class EncodingWorker : public Napi::AsyncWorker {
|
||||
public:
|
||||
EncodingWorker(Napi::Env env): Napi::AsyncWorker(env) {};
|
||||
void Init(void (*work_fn)(Closure*), Closure* closure);
|
||||
void Execute() override;
|
||||
void OnWorkComplete(Napi::Env env, napi_status status) override;
|
||||
|
||||
private:
|
||||
void (*work_fn)(Closure*) = nullptr;
|
||||
Closure* closure = nullptr;
|
||||
};
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
// 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) {
|
||||
float f_channel;
|
||||
if (parse_css_number(pStr, &f_channel)) {
|
||||
int channel = (int) ceil(f_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, °rees)) {
|
||||
*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 WHITESPACE_OR_COMMA_OR_SLASH \
|
||||
while (' ' == *str || ',' == *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 = 0; \
|
||||
float n = .1f; \
|
||||
while(*str >='0' && *str <= '9') { \
|
||||
NAME += (*str - '0') * n; \
|
||||
str++; \
|
||||
} \
|
||||
while(*str == ' ')str++; \
|
||||
if(*str != '%') { \
|
||||
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;
|
||||
float a=1.f;
|
||||
CHANNEL(r);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(g);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(b);
|
||||
WHITESPACE_OR_COMMA_OR_SLASH;
|
||||
ALPHA(a);
|
||||
return *ok = 1, rgba_from_rgba(r, g, b, (int) (255 * a));
|
||||
}
|
||||
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_OR_SLASH;
|
||||
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) {
|
||||
WHITESPACE;
|
||||
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) {
|
||||
WHITESPACE;
|
||||
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
|
||||
);
|
||||
}
|
||||
+30
@@ -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);
|
||||
+20
@@ -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
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// 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 "InstanceData.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
/*
|
||||
* Save some external modules as private references.
|
||||
*/
|
||||
|
||||
static void
|
||||
setDOMMatrix(const Napi::CallbackInfo& info) {
|
||||
InstanceData* data = info.Env().GetInstanceData<InstanceData>();
|
||||
data->DOMMatrixCtor = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
}
|
||||
|
||||
static void
|
||||
setParseFont(const Napi::CallbackInfo& info) {
|
||||
InstanceData* data = info.Env().GetInstanceData<InstanceData>();
|
||||
data->parseFont = Napi::Persistent(info[0].As<Napi::Function>());
|
||||
}
|
||||
|
||||
// Compatibility with Visual Studio versions prior to VS2015
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
||||
env.SetInstanceData(new InstanceData());
|
||||
|
||||
Backends::Initialize(env, exports);
|
||||
Canvas::Initialize(env, exports);
|
||||
Image::Initialize(env, exports);
|
||||
ImageData::Initialize(env, exports);
|
||||
Context2d::Initialize(env, exports);
|
||||
Gradient::Initialize(env, exports);
|
||||
Pattern::Initialize(env, exports);
|
||||
|
||||
exports.Set("setDOMMatrix", Napi::Function::New(env, &setDOMMatrix));
|
||||
exports.Set("setParseFont", Napi::Function::New(env, &setParseFont));
|
||||
|
||||
exports.Set("cairoVersion", Napi::String::New(env, cairo_version_string()));
|
||||
#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);
|
||||
}
|
||||
exports.Set("jpegVersion", Napi::String::New(env, jpeg_version));
|
||||
#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);
|
||||
exports.Set("gifVersion", Napi::String::New(env, gif_version));
|
||||
#else
|
||||
exports.Set("gifVersion", Napi::String::New(env, GIF_LIB_VERSION));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RSVG
|
||||
exports.Set("rsvgVersion", Napi::String::New(env, LIBRSVG_VERSION));
|
||||
#endif
|
||||
|
||||
exports.Set("pangoVersion", Napi::String::New(env, PANGO_VERSION_STRING));
|
||||
|
||||
char freetype_version[10];
|
||||
snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
|
||||
exports.Set("freetypeVersion", Napi::String::New(env, freetype_version));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(canvas, init);
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
#include "register_font.h"
|
||||
|
||||
#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>
|
||||
#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
|
||||
|
||||
#define IS_PREFERRED_ENC(X) \
|
||||
X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
|
||||
|
||||
#define GET_NAME_RANK(X) \
|
||||
(IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
|
||||
|
||||
/*
|
||||
* 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 || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) {
|
||||
char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
|
||||
|
||||
if (buf) {
|
||||
int rank = GET_NAME_RANK(name);
|
||||
if (rank > best_rank) {
|
||||
best_rank = rank;
|
||||
if (best_buf) free(best_buf);
|
||||
best_buf = buf;
|
||||
} 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(desc, family);
|
||||
free(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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
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;
|
||||
}
|
||||
+7
@@ -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);
|
||||
Reference in new issue
Block a user