mirror of
https://github.com/mapbox/node-fontnik.git
synced 2026-01-25 16:02:44 +00:00
327 lines
9.0 KiB
C++
327 lines
9.0 KiB
C++
// fontnik
|
|
#include <node_fontnik/glyphs.hpp>
|
|
|
|
// node
|
|
#include <node_buffer.h>
|
|
#include <nan.h>
|
|
|
|
// freetype2
|
|
extern "C"
|
|
{
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_GLYPH_H
|
|
#include FT_OUTLINE_H
|
|
}
|
|
|
|
namespace node_fontnik
|
|
{
|
|
|
|
struct RangeBaton {
|
|
v8::Persistent<v8::Function> callback;
|
|
Glyphs *glyphs;
|
|
std::string fontstack;
|
|
std::string range;
|
|
std::vector<std::uint32_t> chars;
|
|
bool error;
|
|
std::string error_name;
|
|
};
|
|
|
|
v8::Persistent<v8::FunctionTemplate> Glyphs::constructor;
|
|
|
|
Glyphs::Glyphs() : node::ObjectWrap() {
|
|
glyphs = fontnik::Glyphs();
|
|
}
|
|
|
|
Glyphs::Glyphs(const char *data, size_t length) : node::ObjectWrap() {
|
|
glyphs = fontnik::Glyphs(data, length);
|
|
}
|
|
|
|
Glyphs::~Glyphs() {}
|
|
|
|
void Glyphs::Init(v8::Handle<v8::Object> target) {
|
|
NanScope();
|
|
|
|
v8::Local<v8::FunctionTemplate> tpl = NanNew<v8::FunctionTemplate>(New);
|
|
v8::Local<v8::String> name = NanNew<v8::String>("Glyphs");
|
|
|
|
NanAssignPersistent(Glyphs::constructor, tpl);
|
|
|
|
// node::ObjectWrap uses the first internal field to store the wrapped pointer.
|
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
tpl->SetClassName(name);
|
|
|
|
// Add all prototype methods, getters and setters here.
|
|
NODE_SET_PROTOTYPE_METHOD(tpl, "serialize", Serialize);
|
|
NODE_SET_PROTOTYPE_METHOD(tpl, "range", Range);
|
|
NODE_SET_PROTOTYPE_METHOD(tpl, "codepoints", Codepoints);
|
|
|
|
// This has to be last, otherwise the properties won't show up on the
|
|
// object in JavaScript.
|
|
target->Set(name, constructor->GetFunction());
|
|
}
|
|
|
|
NAN_METHOD(Glyphs::New) {
|
|
NanScope();
|
|
|
|
if (!args.IsConstructCall()) {
|
|
return NanThrowTypeError("Constructor must be called with new keyword");
|
|
}
|
|
if (args.Length() > 0 && !node::Buffer::HasInstance(args[0])) {
|
|
return NanThrowTypeError("First argument may only be a buffer");
|
|
}
|
|
|
|
Glyphs* glyphs;
|
|
|
|
if (args.Length() < 1) {
|
|
glyphs = new Glyphs();
|
|
} else {
|
|
v8::Local<v8::Object> buffer = args[0]->ToObject();
|
|
glyphs = new Glyphs(node::Buffer::Data(buffer), node::Buffer::Length(buffer));
|
|
}
|
|
|
|
glyphs->Wrap(args.This());
|
|
|
|
NanReturnValue(args.This());
|
|
}
|
|
|
|
bool Glyphs::HasInstance(v8::Handle<v8::Value> val) {
|
|
if (!val->IsObject()) return false;
|
|
return constructor->HasInstance(val->ToObject());
|
|
}
|
|
|
|
NAN_METHOD(Glyphs::Serialize) {
|
|
NanScope();
|
|
std::string serialized = node::ObjectWrap::Unwrap<Glyphs>(args.This())->glyphs.Serialize();
|
|
NanReturnValue(NanNewBufferHandle(serialized.data(), serialized.length()));
|
|
}
|
|
|
|
NAN_METHOD(Glyphs::Range) {
|
|
NanScope();
|
|
|
|
// Validate arguments.
|
|
if (args.Length() < 1 || !args[0]->IsString()) {
|
|
return NanThrowTypeError("fontstack must be a string");
|
|
}
|
|
|
|
if (args.Length() < 2 || !args[1]->IsString()) {
|
|
return NanThrowTypeError("range must be a string");
|
|
}
|
|
|
|
if (args.Length() < 3 || !args[2]->IsArray()) {
|
|
return NanThrowTypeError("chars must be an array");
|
|
}
|
|
|
|
if (args.Length() < 4 || !args[3]->IsFunction()) {
|
|
return NanThrowTypeError("callback must be a function");
|
|
}
|
|
|
|
v8::String::Utf8Value fontstack(args[0]->ToString());
|
|
v8::String::Utf8Value range(args[1]->ToString());
|
|
v8::Local<v8::Array> charsArray = v8::Local<v8::Array>::Cast(args[2]);
|
|
v8::Local<v8::Function> callback = v8::Local<v8::Function>::Cast(args[3]);
|
|
|
|
unsigned array_size = charsArray->Length();
|
|
std::vector<std::uint32_t> chars;
|
|
for (unsigned i=0; i < array_size; i++) {
|
|
chars.push_back(charsArray->Get(i)->IntegerValue());
|
|
}
|
|
|
|
Glyphs *glyphs = node::ObjectWrap::Unwrap<Glyphs>(args.This());
|
|
|
|
RangeBaton* baton = new RangeBaton();
|
|
baton->callback = v8::Persistent<v8::Function>::New(callback);
|
|
baton->glyphs = glyphs;
|
|
baton->fontstack = *fontstack;
|
|
baton->range = *range;
|
|
baton->chars = chars;
|
|
|
|
uv_work_t *req = new uv_work_t();
|
|
req->data = baton;
|
|
|
|
int status = uv_queue_work(uv_default_loop(), req, AsyncRange, (uv_after_work_cb)RangeAfter);
|
|
assert(status == 0);
|
|
|
|
NanReturnUndefined();
|
|
}
|
|
|
|
NAN_METHOD(Glyphs::Codepoints) {
|
|
NanScope();
|
|
|
|
// Validate arguments.
|
|
if (args.Length() < 1 || !args[0]->IsString()) {
|
|
return NanThrowTypeError("fontstack must be a string");
|
|
}
|
|
|
|
v8::String::Utf8Value param1(args[0]->ToString());
|
|
std::string from = std::string(*param1);
|
|
try {
|
|
std::vector<int> points = fontnik::Glyphs::Codepoints(from);
|
|
|
|
v8::Handle<v8::Array> result = v8::Array::New(points.size());
|
|
|
|
for (size_t i = 0; i < points.size(); i++) {
|
|
result->Set(i, NanNew<v8::Number>(points[i]));
|
|
}
|
|
NanReturnValue(result);
|
|
} catch (std::exception const& ex) {
|
|
return NanThrowTypeError(ex.what());
|
|
}
|
|
NanReturnUndefined();
|
|
}
|
|
|
|
struct FaceMetadata {
|
|
std::string family_name;
|
|
std::string style_name;
|
|
std::vector<int> points;
|
|
FaceMetadata() :
|
|
family_name(),
|
|
style_name(),
|
|
points() {}
|
|
};
|
|
|
|
struct LoadBaton {
|
|
v8::Persistent<v8::Function> callback;
|
|
std::string file_name;
|
|
std::string error_name;
|
|
std::vector<FaceMetadata> faces;
|
|
uv_work_t request;
|
|
LoadBaton() :
|
|
file_name(),
|
|
error_name(),
|
|
faces() {}
|
|
};
|
|
|
|
NAN_METHOD(Load) {
|
|
NanScope();
|
|
|
|
// Validate arguments.
|
|
if (!args[0]->IsString()) {
|
|
return NanThrowTypeError("First argument must be a path to a font");
|
|
}
|
|
if (args.Length() < 2 || !args[1]->IsFunction()) {
|
|
return NanThrowTypeError("callback must be a function");
|
|
}
|
|
|
|
v8::Local<v8::Function> callback = args[1].As<v8::Function>();
|
|
|
|
LoadBaton* baton = new LoadBaton();
|
|
baton->file_name = *NanUtf8String(args[0]);
|
|
|
|
baton->request.data = baton;
|
|
NanAssignPersistent(baton->callback, callback.As<v8::Function>());
|
|
|
|
uv_queue_work(uv_default_loop(), &baton->request, LoadAsync, (uv_after_work_cb)AfterLoad);
|
|
NanReturnUndefined();
|
|
}
|
|
|
|
void LoadAsync(uv_work_t* req) {
|
|
LoadBaton* baton = static_cast<LoadBaton*>(req->data);
|
|
|
|
FT_Library library = nullptr;
|
|
FT_Error error = FT_Init_FreeType(&library);
|
|
if (error) {
|
|
baton->error_name = std::string("could not open FreeType library");
|
|
return;
|
|
}
|
|
std::vector<FaceMetadata> faces;
|
|
FT_Face ft_face = 0;
|
|
int num_faces = 0;
|
|
for ( int i = 0; ft_face == 0 || i < num_faces; ++i )
|
|
{
|
|
FT_Error face_error = FT_New_Face(library, baton->file_name.c_str(), i, &ft_face);
|
|
if (face_error) {
|
|
baton->error_name = std::string("could not open Face") + baton->file_name;
|
|
return;
|
|
}
|
|
FaceMetadata face;
|
|
std::vector<int> points;
|
|
if (num_faces == 0)
|
|
num_faces = ft_face->num_faces;
|
|
FT_ULong charcode;
|
|
FT_UInt gindex;
|
|
charcode = FT_Get_First_Char(ft_face, &gindex);
|
|
while (gindex != 0) {
|
|
charcode = FT_Get_Next_Char(ft_face, charcode, &gindex);
|
|
if (charcode != 0) points.push_back(charcode);
|
|
}
|
|
|
|
std::sort(points.begin(), points.end());
|
|
auto last = std::unique(points.begin(), points.end());
|
|
points.erase(last, points.end());
|
|
|
|
face.points = std::move(points);
|
|
face.family_name = ft_face->family_name;
|
|
face.style_name = ft_face->style_name;
|
|
|
|
faces.push_back(std::move(face));
|
|
if (ft_face) {
|
|
FT_Done_Face(ft_face);
|
|
}
|
|
}
|
|
baton->faces = std::move(faces);
|
|
FT_Done_FreeType(library);
|
|
|
|
};
|
|
|
|
void AfterLoad(uv_work_t* req) {
|
|
NanScope();
|
|
LoadBaton* baton = static_cast<LoadBaton*>(req->data);
|
|
|
|
if (!baton->error_name.empty()) {
|
|
v8::Local<v8::Value> argv[1] = { NanError(baton->error_name.c_str()) };
|
|
NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv);
|
|
} else {
|
|
v8::Local<v8::Value> argv[2] = { NanNull(), NanNull() /* todo return custom object */ };
|
|
NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv);
|
|
}
|
|
|
|
NanDisposePersistent(baton->callback);
|
|
delete baton;
|
|
};
|
|
|
|
void Glyphs::AsyncRange(uv_work_t* req) {
|
|
RangeBaton* baton = static_cast<RangeBaton*>(req->data);
|
|
|
|
try {
|
|
baton->glyphs->glyphs.Range(baton->fontstack, baton->range, baton->chars);
|
|
} catch(const std::runtime_error &e) {
|
|
baton->error = true;
|
|
baton->error_name = e.what();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Glyphs::RangeAfter(uv_work_t* req) {
|
|
NanScope();
|
|
RangeBaton* baton = static_cast<RangeBaton*>(req->data);
|
|
|
|
const unsigned argc = 1;
|
|
|
|
v8::TryCatch try_catch;
|
|
v8::Local<v8::Context> ctx = NanGetCurrentContext();
|
|
|
|
if (baton->error) {
|
|
v8::Local<v8::Value> argv[argc] = {
|
|
v8::Exception::Error(NanNew<v8::String>(baton->error_name.c_str()))
|
|
};
|
|
baton->callback->Call(ctx->Global(), argc, argv);
|
|
} else {
|
|
v8::Local<v8::Value> argv[argc] = {
|
|
NanNull()
|
|
};
|
|
baton->callback->Call(ctx->Global(), argc, argv);
|
|
}
|
|
|
|
if (try_catch.HasCaught()) {
|
|
node::FatalException(try_catch);
|
|
}
|
|
|
|
baton->callback.Dispose();
|
|
|
|
delete baton;
|
|
delete req;
|
|
}
|
|
|
|
} // ns node_fontnik
|