Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions bench/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var path = require('path');
var fontnik = require('../');
var queue = require('queue-async');
var fs = require('fs');

// https://gist.github.com/mourner/96b1335c6a43e68af252
// https://gist.github.com/fengmk2/4345606
Expand All @@ -25,7 +26,7 @@ function bench(opts,cb) {
}

function main() {
var opensans = path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf');
var opensans = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf'));

var suite = queue(1);
suite.defer(bench, {
Expand All @@ -36,7 +37,7 @@ function main() {
});
suite.defer(bench, {
name:"fontnik.range",
args:[fontnik.range,{file:opensans,start:0,end:256}],
args:[fontnik.range,{font:opensans,start:0,end:256}],
iterations: 1000,
concurrency: 100
});
Expand Down
5 changes: 3 additions & 2 deletions bench/bench2.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
var path = require('path');
var fontnik = require('../');
var Benchmark = require('benchmark');
var fs = require('fs');

var opensans = path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf');
var opensans = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf'));

var suite = new Benchmark.Suite();

Expand All @@ -25,7 +26,7 @@ suite
'fn': function(deferred) {
// avoid test inlining
suite.name;
fontnik.range({file:opensans,start:0,end:256},function(err) {
fontnik.range({font:opensans,start:0,end:256},function(err) {
if (err) throw err;
deferred.resolve();
});
Expand Down
116 changes: 67 additions & 49 deletions src/glyphs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,53 +46,79 @@ struct FaceMetadata {

struct LoadBaton {
v8::Persistent<v8::Function> callback;
std::string file_name;
v8::Persistent<v8::Object> buffer;
const char * font_data;
std::size_t font_size;
std::string error_name;
std::vector<FaceMetadata> faces;
uv_work_t request;
LoadBaton() :
file_name(),
LoadBaton(v8::Local<v8::Object> buf,
v8::Local<v8::Value> cb) :
font_data(node::Buffer::Data(buf)),
font_size(node::Buffer::Length(buf)),
error_name(),
faces() {}
faces(),
request() {
request.data = this;
NanAssignPersistent(callback, cb.As<v8::Function>());
NanAssignPersistent(buffer, buf.As<v8::Object>());
}
~LoadBaton() {
NanDisposePersistent(callback);
NanDisposePersistent(buffer);
}
};

struct RangeBaton {
v8::Persistent<v8::Function> callback;
std::string file_name;
v8::Persistent<v8::Object> buffer;
const char * font_data;
std::size_t font_size;
std::string error_name;
std::uint32_t start;
std::uint32_t end;
std::vector<std::uint32_t> chars;
std::string message;
uv_work_t request;
RangeBaton() :
file_name(),
RangeBaton(v8::Local<v8::Object> buf,
v8::Local<v8::Value> cb,
std::uint32_t _start,
std::uint32_t _end) :
font_data(node::Buffer::Data(buf)),
font_size(node::Buffer::Length(buf)),
error_name(),
start(),
end(),
start(_start),
end(_end),
chars(),
message() {}
message(),
request() {
request.data = this;
NanAssignPersistent(callback, cb.As<v8::Function>());
NanAssignPersistent(buffer, buf.As<v8::Object>());
}
~RangeBaton() {
NanDisposePersistent(callback);
NanDisposePersistent(buffer);
}
};

NAN_METHOD(Load) {
NanScope();

// Validate arguments.
if (!args[0]->IsString()) {
return NanThrowTypeError("First argument must be a path to a font");
if (!args[0]->IsObject()) {
return NanThrowTypeError("First argument must be a font buffer");
}
v8::Local<v8::Object> obj = args[0]->ToObject();
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
return NanThrowTypeError("First argument must be a font buffer");
}

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>());

LoadBaton* baton = new LoadBaton(obj,args[1]);
uv_queue_work(uv_default_loop(), &baton->request, LoadAsync, (uv_after_work_cb)AfterLoad);
NanReturnUndefined();
}
Expand All @@ -106,16 +132,16 @@ NAN_METHOD(Range) {
}

v8::Local<v8::Object> options = args[0].As<v8::Object>();
v8::Local<v8::Value> file_name = options->Get(NanNew<v8::String>("file"));
v8::Local<v8::Value> font_buffer = options->Get(NanNew<v8::String>("font"));
if (!font_buffer->IsObject()) {
return NanThrowTypeError("Font buffer is not an object");
}
v8::Local<v8::Object> obj = font_buffer->ToObject();
v8::Local<v8::Value> start = options->Get(NanNew<v8::String>("start"));
v8::Local<v8::Value> end = options->Get(NanNew<v8::String>("end"));

if (!file_name->IsString()) {
return NanThrowTypeError("option `file` must be a path to a font");
}
std::string filename = *NanUtf8String(file_name);
if (filename.empty()) {
return NanThrowTypeError("option `file` cannot be empty");
if (obj->IsNull() || obj->IsUndefined() || !node::Buffer::HasInstance(obj)) {
return NanThrowTypeError("First argument must be a font buffer");
}

if (!start->IsNumber() || start->IntegerValue() < 0) {
Expand All @@ -130,25 +156,14 @@ NAN_METHOD(Range) {
return NanThrowTypeError("`start` must be less than or equal to `end`");
}

RangeBaton* baton = new RangeBaton();
baton->file_name = std::move(filename);
baton->start = start->IntegerValue();
baton->end = end->IntegerValue();

if (args.Length() < 2 || !args[1]->IsFunction()) {
return NanThrowTypeError("Callback must be a function");
}

v8::Local<v8::Function> callback = args[1].As<v8::Function>();

unsigned array_size = baton->end - baton->start;
for (unsigned i=baton->start; i <= array_size; i++) {
baton->chars.emplace_back(i);
}

baton->request.data = baton;
NanAssignPersistent(baton->callback, callback.As<v8::Function>());

RangeBaton* baton = new RangeBaton(obj,
args[1],
start->IntegerValue(),
end->IntegerValue());
uv_queue_work(uv_default_loop(), &baton->request, RangeAsync, (uv_after_work_cb)AfterRange);
NanReturnUndefined();
}
Expand All @@ -168,9 +183,9 @@ void LoadAsync(uv_work_t* req) {
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);
FT_Error face_error = FT_New_Memory_Face(library, reinterpret_cast<FT_Byte const*>(baton->font_data), static_cast<FT_Long>(baton->font_size), i, &ft_face);
if (face_error) {
baton->error_name = std::string("could not open Face") + baton->file_name;
baton->error_name = std::string("could not open font file");
return;
}
std::set<int> points;
Expand Down Expand Up @@ -218,14 +233,18 @@ void AfterLoad(uv_work_t* req) {
v8::Local<v8::Value> argv[2] = { NanNull(), js_faces };
NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv);
}

NanDisposePersistent(baton->callback);
delete baton;
};

void RangeAsync(uv_work_t* req) {
RangeBaton* baton = static_cast<RangeBaton*>(req->data);

unsigned array_size = baton->end - baton->start;
baton->chars.reserve(array_size);
for (unsigned i=baton->start; i <= array_size; i++) {
baton->chars.emplace_back(i);
}

FT_Library library = nullptr;
FT_Error error = FT_Init_FreeType(&library);
if (error) {
Expand All @@ -242,9 +261,9 @@ void RangeAsync(uv_work_t* req) {
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);
FT_Error face_error = FT_New_Memory_Face(library, reinterpret_cast<FT_Byte const*>(baton->font_data), static_cast<FT_Long>(baton->font_size), i, &ft_face);
if (face_error) {
baton->error_name = std::string("could not open Face: '") + baton->file_name + "'";
baton->error_name = std::string("could not open font");
return;
}

Expand Down Expand Up @@ -308,7 +327,6 @@ void AfterRange(uv_work_t* req) {
NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv);
}

NanDisposePersistent(baton->callback);
delete baton;
};

Expand Down
51 changes: 18 additions & 33 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ function jsonEqual(key, json) {
}

var expected = JSON.parse(fs.readFileSync(__dirname + '/expected/load.json').toString());
var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf');
var opensans = path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf');
var firasans = fs.readFileSync(path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'));
var opensans = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf'));
describe('load', function() {
it('loads: fira sans', function(done) {
assert.ok(fs.existsSync(firasans));
fontnik.load(firasans, function(err, faces) {
assert.ifError(err);
assert.deepEqual(faces,expected);
Expand All @@ -44,14 +43,14 @@ describe('load', function() {
var baloneysans;
assert.throws(function() {
fontnik.load(baloneysans, function(err, faces) {});
}, /First argument must be a path to a font/);
}, /First argument must be a font buffer/);
done();
});

it('non existent font loading', function(done) {
var doesnotexistsans = opensans.replace('Regular','baloney');
var doesnotexistsans = new Buffer('baloney');
fontnik.load(doesnotexistsans, function(err, faces) {
assert.ok(err.message.indexOf('could not open face'));
assert.ok(err.message.indexOf('Font buffer is not an object'));
done();
});
});
Expand All @@ -77,7 +76,7 @@ describe('range', function() {

it('ranges', function(done) {
this.timeout(10000);
fontnik.range({file: opensans, start: 0, end: 256}, function(err, res) {
fontnik.range({font: opensans, start: 0, end: 256}, function(err, res) {
assert.ifError(err);
assert.ok(res);
assert.deepEqual(res, data);
Expand All @@ -90,7 +89,7 @@ describe('range', function() {

it('longrange', function(done) {
this.timeout(10000);
fontnik.range({file: opensans, start: 0, end: 1024}, function(err, data) {
fontnik.range({font: opensans, start: 0, end: 1024}, function(err, data) {
assert.ifError(err);
assert.ok(data);
done();
Expand All @@ -101,65 +100,51 @@ describe('range', function() {
it('range typeerror options', function(done) {
assert.throws(function() {
fontnik.range(opensans, function(err, data) {});
}, /First argument must be an object of options/);
}, /Font buffer is not an object/);
done();
});

it('range filepath does not exist', function(done) {
var doesnotexistsans = opensans.replace('Regular','baloney');
fontnik.range({file: doesnotexistsans, start: 0, end: 256}, function(err, faces) {
assert.ok(err.message.indexOf('could not open face'));
var doesnotexistsans = new Buffer('baloney');
fontnik.range({font: doesnotexistsans, start: 0, end: 256}, function(err, faces) {
assert.ok(err.message.indexOf('Font buffer is not an object'));
done();
});
});

it('range typeerror empty filepath', function(done) {
assert.throws(function() {
fontnik.range({file: '', start: 0, end: 256}, function(err, data) {});
}, /option `file` cannot be empty/);
done();
});

it('range typeerror filepath', function(done) {
assert.throws(function() {
fontnik.range({file: 12, start: 0, end: 256}, function(err, data) {});
}, /option `file` must be a path to a font/);
done();
});

it('range typeerror start', function(done) {
assert.throws(function() {
fontnik.range({file: opensans, start: 'x', end: 256}, function(err, data) {});
fontnik.range({font: opensans, start: 'x', end: 256}, function(err, data) {});
}, /option `start` must be a number from 0-65535/);
assert.throws(function() {
fontnik.range({file: opensans, start: -3, end: 256}, function(err, data) {});
fontnik.range({font: opensans, start: -3, end: 256}, function(err, data) {});
}, /option `start` must be a number from 0-65535/);
done();
});

it('range typeerror end', function(done) {
assert.throws(function() {
fontnik.range({file: opensans, start: 0, end: 'y'}, function(err, data) {});
fontnik.range({font: opensans, start: 0, end: 'y'}, function(err, data) {});
}, /option `end` must be a number from 0-65535/);
assert.throws(function() {
fontnik.range({file: opensans, start: 0, end: 10000000}, function(err, data) {});
fontnik.range({font: opensans, start: 0, end: 10000000}, function(err, data) {});
}, /option `end` must be a number from 0-65535/);
done();
});

it('range typeerror lt', function(done) {
assert.throws(function() {
fontnik.range({file: opensans, start: 256, end: 0}, function(err, data) {});
fontnik.range({font: opensans, start: 256, end: 0}, function(err, data) {});
}, /`start` must be less than or equal to `end`/);
done();
});

it('range typeerror callback', function(done) {
assert.throws(function() {
fontnik.range({file: opensans, start: 0, end: 256}, '');
fontnik.range({font: opensans, start: 0, end: 256}, '');
}, /Callback must be a function/);
assert.throws(function() {
fontnik.range({file: opensans, start: 0, end: 256});
fontnik.range({font: opensans, start: 0, end: 256});
}, /Callback must be a function/);
done();
});
Expand Down