From 79343844931aa9fe843022011cda9c660808df39 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Tue, 17 Mar 2015 17:29:27 -0700 Subject: [PATCH 01/41] Start load function --- include/node_fontnik/glyphs.hpp | 1 + src/glyphs.cpp | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/include/node_fontnik/glyphs.hpp b/include/node_fontnik/glyphs.hpp index 13e366543..8221ac64d 100644 --- a/include/node_fontnik/glyphs.hpp +++ b/include/node_fontnik/glyphs.hpp @@ -23,6 +23,7 @@ class Glyphs : public node::ObjectWrap { static NAN_METHOD(Serialize); static NAN_METHOD(Range); static NAN_METHOD(Codepoints); + static NAN_METHOD(Load); static void AsyncRange(uv_work_t* req); static void RangeAfter(uv_work_t* req); diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 2697bb351..df7f401ed 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -161,6 +161,52 @@ NAN_METHOD(Glyphs::Codepoints) { NanReturnUndefined(); } +struct LoadBaton { + v8::Persistent callback; + std::string file_name; + std::string error_name; + uv_work_t request; + LoadBaton() : + file_name(), + error_name() {} +}; + + +NAN_METHOD(Glyphs::Load) { + NanScope(); + + // Validate arguments. + // args[0]? + if (args.Length() < 2 || !args[1]->IsFunction()) { + return NanThrowTypeError("callback must be a function"); + } + + v8::Local cb = args[1].As(); + + LoadBaton* baton = new LoadBaton(); + + baton->request.data = baton; + NanAssignPersistent(baton->cb, cb.As()); + + uv_queue_work(uv_default_loop(), &cb->request, LoadAsync, (uv_after_work_cb)AfterLoad); + NanReturnUndefined(); +} + +void LoadAsync(uv_work_t* req) { + LoadBaton* baton = static_cast(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; + } +}; + +void AfterLoad(uv_work_t* req) { + +}; + void Glyphs::AsyncRange(uv_work_t* req) { RangeBaton* baton = static_cast(req->data); From d4700c265486a165932fdd6ea7e8d92a75aedf9c Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Wed, 18 Mar 2015 17:31:33 -0700 Subject: [PATCH 02/41] .load compiling and crashing --- include/node_fontnik/glyphs.hpp | 9 +- src/glyphs.cpp | 99 ++++++++- src/node_fontnik.cpp | 2 + test/test.js | 352 ++++++++++++++++---------------- 4 files changed, 280 insertions(+), 182 deletions(-) diff --git a/include/node_fontnik/glyphs.hpp b/include/node_fontnik/glyphs.hpp index 8221ac64d..99165a6b3 100644 --- a/include/node_fontnik/glyphs.hpp +++ b/include/node_fontnik/glyphs.hpp @@ -20,16 +20,19 @@ class Glyphs : public node::ObjectWrap { ~Glyphs(); static NAN_METHOD(New); - static NAN_METHOD(Serialize); static NAN_METHOD(Range); - static NAN_METHOD(Codepoints); - static NAN_METHOD(Load); static void AsyncRange(uv_work_t* req); static void RangeAfter(uv_work_t* req); + static NAN_METHOD(Serialize); + static NAN_METHOD(Codepoints); fontnik::Glyphs glyphs; }; +NAN_METHOD(Load); +void LoadAsync(uv_work_t* req); +void AfterLoad(uv_work_t* req); + } // ns node_fontnik #endif // NODE_FONTNIK_GLYPHS_HPP diff --git a/src/glyphs.cpp b/src/glyphs.cpp index df7f401ed..6d0e1ed23 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -5,6 +5,15 @@ #include #include +// freetype2 +extern "C" +{ +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +} + namespace node_fontnik { @@ -161,34 +170,48 @@ NAN_METHOD(Glyphs::Codepoints) { NanReturnUndefined(); } +struct FaceMetadata { + std::string family_name; + std::string style_name; + std::vector points; + FaceMetadata() : + family_name(), + style_name(), + points() {} +}; + struct LoadBaton { v8::Persistent callback; std::string file_name; std::string error_name; + bool error; + std::vector faces; uv_work_t request; LoadBaton() : - file_name(), - error_name() {} + file_name(), + error_name(), + faces() {} }; - -NAN_METHOD(Glyphs::Load) { +NAN_METHOD(Load) { NanScope(); // Validate arguments. - // args[0]? + if (!args[0]->IsString()) { + return NanThrowTypeError(""); + } if (args.Length() < 2 || !args[1]->IsFunction()) { return NanThrowTypeError("callback must be a function"); } - v8::Local cb = args[1].As(); + v8::Local callback = args[1].As(); LoadBaton* baton = new LoadBaton(); baton->request.data = baton; - NanAssignPersistent(baton->cb, cb.As()); + NanAssignPersistent(baton->callback, callback.As()); - uv_queue_work(uv_default_loop(), &cb->request, LoadAsync, (uv_after_work_cb)AfterLoad); + uv_queue_work(uv_default_loop(), &baton->request, LoadAsync, (uv_after_work_cb)AfterLoad); NanReturnUndefined(); } @@ -198,13 +221,73 @@ void LoadAsync(uv_work_t* req) { FT_Library library = nullptr; FT_Error error = FT_Init_FreeType(&library); if (error) { + baton->error = true; baton->error_name = std::string("could not open FreeType library"); return; } + + std::vector faces; + FaceMetadata face; + std::vector points; + + FT_Face ft_face = nullptr; + + FT_New_Face(library, baton->file_name.c_str(), 0, &ft_face); + + for ( int i = 0; ft_face == 0 || i < ft_face->num_faces; ++i ) + { + 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)); + } + + baton->faces = std::move(faces); + + FT_Done_Face(ft_face); }; void AfterLoad(uv_work_t* req) { + NanScope(); + LoadBaton* baton = static_cast(req->data); + + const unsigned argc = 1; + v8::TryCatch try_catch; + v8::Local ctx = NanGetCurrentContext(); + + if (baton->error) { + v8::Local argv[argc] = { + v8::Exception::Error(NanNew(baton->error_name.c_str())) + }; + baton->callback->Call(ctx->Global(), argc, argv); + } else { + v8::Local 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; }; void Glyphs::AsyncRange(uv_work_t* req) { diff --git a/src/node_fontnik.cpp b/src/node_fontnik.cpp index 59ac4bfeb..1269f217e 100644 --- a/src/node_fontnik.cpp +++ b/src/node_fontnik.cpp @@ -15,6 +15,8 @@ void RegisterModule(v8::Handle target) { NODE_SET_METHOD(target, "register_fonts", node_mapnik_fontnik::register_fonts); NODE_SET_METHOD(target, "faces", node_mapnik_fontnik::available_font_faces); NODE_SET_METHOD(target, "files", node_mapnik_fontnik::available_font_files); + NODE_SET_METHOD(target, "load", node_fontnik::Load); + // NODE_SET_METHOD(target, "range", node_fontnik::Range); Glyphs::Init(target); } diff --git a/test/test.js b/test/test.js index 91f1c3059..fe1fb788a 100644 --- a/test/test.js +++ b/test/test.js @@ -8,178 +8,188 @@ var Protobuf = require('pbf'); var Glyphs = require('./format/glyphs'); var UPDATE = process.env.UPDATE; -function nobuffer(key, val) { - return key !== '_buffer' && key !== 'bitmap' ? val : undefined; -} - -function jsonEqual(key, json) { - if (UPDATE) fs.writeFileSync(__dirname + '/expected/'+key+'.json', JSON.stringify(json, null, 2)); - assert.deepEqual(json, require('./expected/'+key+'.json')); -} - -fontnik.register_fonts(path.resolve(__dirname + '/../fonts/'), { recurse: true }); - -describe('glyphs', function() { - var data; - before(function(done) { - zlib.inflate(zdata, function(err, d) { - assert.ifError(err); - data = d; - done(); - }); - }); - - it('serialize', function(done) { - // On disk fixture generated with the following code. - if (UPDATE) { - fontnik.range({ - fontstack:'Open Sans Regular', - start: 0, - end: 256 - }, function(err, zdata) { - if (err) throw err; - fs.writeFileSync(__dirname + '/fixtures/range.0.256.pbf', zdata); - done(); - }); - } - var glyphs = new fontnik.Glyphs(data); - var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); - var json = JSON.parse(JSON.stringify(vt, nobuffer)); - jsonEqual('range', json); - done(); - }); - - it('range', function(done) { - var glyphs = new fontnik.Glyphs(); - glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), function(err) { - assert.ifError(err); - var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); - var json = JSON.parse(JSON.stringify(vt, nobuffer)); - jsonEqual('range', json); - done(); - }); - }); - - // Render a long range of characters which can cause segfaults - // with V8 arrays ... not sure yet why. - it('longrange', function(done) { - var glyphs = new fontnik.Glyphs(); - glyphs.range('Open Sans Regular', '0-1024', fontnik.getRange(0, 1024), function(err) { - assert.ifError(err); - done(); - }); - }); - - it('range (chars input)', function(done) { - var glyphs = new fontnik.Glyphs(); - glyphs.range('Open Sans Regular', 'a-and-z', [('a').charCodeAt(0), ('z').charCodeAt(0)], function(err) { +// function nobuffer(key, val) { +// return key !== '_buffer' && key !== 'bitmap' ? val : undefined; +// } +// +// function jsonEqual(key, json) { +// if (UPDATE) fs.writeFileSync(__dirname + '/expected/'+key+'.json', JSON.stringify(json, null, 2)); +// assert.deepEqual(json, require('./expected/'+key+'.json')); +// } +// +// fontnik.register_fonts(path.resolve(__dirname + '/../fonts/'), { recurse: true }); +// +// describe('glyphs', function() { +// var data; +// before(function(done) { +// zlib.inflate(zdata, function(err, d) { +// assert.ifError(err); +// data = d; +// done(); +// }); +// }); +// +// it('serialize', function(done) { +// // On disk fixture generated with the following code. +// if (UPDATE) { +// fontnik.range({ +// fontstack:'Open Sans Regular', +// start: 0, +// end: 256 +// }, function(err, zdata) { +// if (err) throw err; +// fs.writeFileSync(__dirname + '/fixtures/range.0.256.pbf', zdata); +// done(); +// }); +// } +// var glyphs = new fontnik.Glyphs(data); +// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); +// var json = JSON.parse(JSON.stringify(vt, nobuffer)); +// jsonEqual('range', json); +// done(); +// }); +// +// it('range', function(done) { +// var glyphs = new fontnik.Glyphs(); +// glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), function(err) { +// assert.ifError(err); +// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); +// var json = JSON.parse(JSON.stringify(vt, nobuffer)); +// jsonEqual('range', json); +// done(); +// }); +// }); +// +// // Render a long range of characters which can cause segfaults +// // with V8 arrays ... not sure yet why. +// it('longrange', function(done) { +// var glyphs = new fontnik.Glyphs(); +// glyphs.range('Open Sans Regular', '0-1024', fontnik.getRange(0, 1024), function(err) { +// assert.ifError(err); +// done(); +// }); +// }); +// +// it('range (chars input)', function(done) { +// var glyphs = new fontnik.Glyphs(); +// glyphs.range('Open Sans Regular', 'a-and-z', [('a').charCodeAt(0), ('z').charCodeAt(0)], function(err) { +// assert.ifError(err); +// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); +// var json = JSON.parse(JSON.stringify(vt, nobuffer)); +// jsonEqual('chars', json); +// done(); +// }); +// }); +// +// it('range typeerror fontstack', function(done) { +// var glyphs = new fontnik.Glyphs(); +// assert.throws(function() { +// glyphs.range(0, '0-256', fontnik.getRange(0, 256), function() {}); +// }, /fontstack must be a string/); +// done(); +// }); +// +// it('range typeerror range', function(done) { +// var glyphs = new fontnik.Glyphs(); +// assert.throws(function() { +// glyphs.range('Open Sans Regular', 0, fontnik.getRange(0, 256), function() {}); +// }, /range must be a string/); +// done(); +// }); +// +// it('range typeerror chars', function(done) { +// var glyphs = new fontnik.Glyphs(); +// assert.throws(function() { +// glyphs.range('Open Sans Regular', '0-256', 'foo', function() {}); +// }, /chars must be an array/); +// done(); +// }); +// +// it('range typeerror callback', function(done) { +// var glyphs = new fontnik.Glyphs(); +// assert.throws(function() { +// glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), ''); +// }, /callback must be a function/); +// done(); +// }); +// +// it('range for fontstack with 0 matching fonts', function(done) { +// var glyphs = new fontnik.Glyphs(); +// glyphs.range('doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { +// assert.ok(err); +// assert.equal("Error: Failed to find face 'doesnotexist' in font set 'doesnotexist'", err.toString()); +// done(); +// }); +// }); +// +// it('range for fontstack with 1 bad font', function(done) { +// var glyphs = new fontnik.Glyphs(); +// glyphs.range('Open Sans Regular, doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { +// assert.ok(err); +// assert.equal("Error: Failed to find face 'doesnotexist' in font set 'Open Sans Regular, doesnotexist'", err.toString()); +// done(); +// }); +// }); +// +// // Should error because start is < 0 +// it('getRange error start < 0', function() { +// assert.throws(function() { +// fontnik.getRange(-128, 256); +// }, 'Error: start must be a number from 0-65535'); +// }); +// +// // Should error because end < start +// it('getRange error end < start', function() { +// assert.throws(function() { +// fontnik.getRange(256, 0); +// }, 'Error: start must be less than or equal to end'); +// }); +// +// // Should error because end > 65535 +// it('getRange error end > 65535', function() { +// assert.throws(function() { +// fontnik.getRange(0, 65536); +// }, 'Error: end must be a number from 0-65535'); +// }); +// }); +// +// describe('codepoints', function() { +// it('basic scanning: open sans', function() { +// var glyphs = new fontnik.Glyphs(); +// var cp = glyphs.codepoints('Open Sans Regular'); +// assert.equal(cp.length, 882); +// }); +// it('basic scanning: fira sans', function() { +// var glyphs = new fontnik.Glyphs(); +// var cp = glyphs.codepoints('Fira Sans Medium'); +// assert.equal(cp.length, 789); +// }); +// it('basic scanning: fira sans + open sans', function() { +// var glyphs = new fontnik.Glyphs(); +// var cp = glyphs.codepoints('Fira Sans Medium, Open Sans Regular'); +// assert.equal(cp.length, 1021); +// }); +// it('invalid font face', function() { +// var glyphs = new fontnik.Glyphs(); +// assert.throws(function() { +// glyphs.codepoints('foo-bar-invalid'); +// }); +// }); +// }); +// +// describe('faces', function() { +// it('list faces', function() { +// var faces = fontnik.faces(); +// assert.deepEqual(faces, ['Fira Sans Medium', 'Open Sans Regular']); +// }); +// }); + +describe('load', function() { + it('loads', function() { + var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'); + fontnik.load(firasans, function(err, res) { + console.log(err, res); assert.ifError(err); - var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); - var json = JSON.parse(JSON.stringify(vt, nobuffer)); - jsonEqual('chars', json); - done(); }); }); - - it('range typeerror fontstack', function(done) { - var glyphs = new fontnik.Glyphs(); - assert.throws(function() { - glyphs.range(0, '0-256', fontnik.getRange(0, 256), function() {}); - }, /fontstack must be a string/); - done(); - }); - - it('range typeerror range', function(done) { - var glyphs = new fontnik.Glyphs(); - assert.throws(function() { - glyphs.range('Open Sans Regular', 0, fontnik.getRange(0, 256), function() {}); - }, /range must be a string/); - done(); - }); - - it('range typeerror chars', function(done) { - var glyphs = new fontnik.Glyphs(); - assert.throws(function() { - glyphs.range('Open Sans Regular', '0-256', 'foo', function() {}); - }, /chars must be an array/); - done(); - }); - - it('range typeerror callback', function(done) { - var glyphs = new fontnik.Glyphs(); - assert.throws(function() { - glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), ''); - }, /callback must be a function/); - done(); - }); - - it('range for fontstack with 0 matching fonts', function(done) { - var glyphs = new fontnik.Glyphs(); - glyphs.range('doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { - assert.ok(err); - assert.equal("Error: Failed to find face 'doesnotexist' in font set 'doesnotexist'", err.toString()); - done(); - }); - }); - - it('range for fontstack with 1 bad font', function(done) { - var glyphs = new fontnik.Glyphs(); - glyphs.range('Open Sans Regular, doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { - assert.ok(err); - assert.equal("Error: Failed to find face 'doesnotexist' in font set 'Open Sans Regular, doesnotexist'", err.toString()); - done(); - }); - }); - - // Should error because start is < 0 - it('getRange error start < 0', function() { - assert.throws(function() { - fontnik.getRange(-128, 256); - }, 'Error: start must be a number from 0-65535'); - }); - - // Should error because end < start - it('getRange error end < start', function() { - assert.throws(function() { - fontnik.getRange(256, 0); - }, 'Error: start must be less than or equal to end'); - }); - - // Should error because end > 65535 - it('getRange error end > 65535', function() { - assert.throws(function() { - fontnik.getRange(0, 65536); - }, 'Error: end must be a number from 0-65535'); - }); -}); - -describe('codepoints', function() { - it('basic scanning: open sans', function() { - var glyphs = new fontnik.Glyphs(); - var cp = glyphs.codepoints('Open Sans Regular'); - assert.equal(cp.length, 882); - }); - it('basic scanning: fira sans', function() { - var glyphs = new fontnik.Glyphs(); - var cp = glyphs.codepoints('Fira Sans Medium'); - assert.equal(cp.length, 789); - }); - it('basic scanning: fira sans + open sans', function() { - var glyphs = new fontnik.Glyphs(); - var cp = glyphs.codepoints('Fira Sans Medium, Open Sans Regular'); - assert.equal(cp.length, 1021); - }); - it('invalid font face', function() { - var glyphs = new fontnik.Glyphs(); - assert.throws(function() { - glyphs.codepoints('foo-bar-invalid'); - }); - }); -}); - -describe('faces', function() { - it('list faces', function() { - var faces = fontnik.faces(); - assert.deepEqual(faces, ['Fira Sans Medium', 'Open Sans Regular']); - }); }); From 2101befaf064d75be1b3820d5485261db3156189 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:27:28 -0700 Subject: [PATCH 03/41] upgrade node-pre-gyp --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2f9c4ff7..38951225f 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "minimist": "^0.2.0", "nan": "^1.2.0", - "node-pre-gyp": "^0.5.31", + "node-pre-gyp": "~0.6.4", "queue-async": "^1.0.7" }, "bundledDependencies": [ From 9971b022ec68d8c03d65dc8510c6483193646318 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:28:00 -0700 Subject: [PATCH 04/41] convert test to async + check font exits --- test/test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index fe1fb788a..47470f5d9 100644 --- a/test/test.js +++ b/test/test.js @@ -185,11 +185,13 @@ var UPDATE = process.env.UPDATE; // }); describe('load', function() { - it('loads', function() { + it('loads', function(done) { var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'); + assert.ok(fs.existsSync(firasans)); fontnik.load(firasans, function(err, res) { console.log(err, res); assert.ifError(err); + done(); }); }); }); From f59833196cee6d2056a01a970c85e9e22aa12850 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:29:47 -0700 Subject: [PATCH 05/41] pass file_name string to baton --- src/glyphs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 6d0e1ed23..8dd62c524 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -198,7 +198,7 @@ NAN_METHOD(Load) { // Validate arguments. if (!args[0]->IsString()) { - return NanThrowTypeError(""); + 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"); @@ -207,6 +207,7 @@ NAN_METHOD(Load) { v8::Local callback = args[1].As(); LoadBaton* baton = new LoadBaton(); + baton->file_name = *NanUtf8String(args[0]); baton->request.data = baton; NanAssignPersistent(baton->callback, callback.As()); From b5205b128f00e5498ec86392fc0574ef4636bef6 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:30:30 -0700 Subject: [PATCH 06/41] req is not heap allocated so no need to delete --- src/glyphs.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 8dd62c524..2509b4bf3 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -288,7 +288,6 @@ void AfterLoad(uv_work_t* req) { baton->callback.Dispose(); delete baton; - delete req; }; void Glyphs::AsyncRange(uv_work_t* req) { From 5cf9c948981a64a6c7c78860aba81ce2098cc8f8 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:32:06 -0700 Subject: [PATCH 07/41] simplify + use more of Nan API in AfterLoad --- src/glyphs.cpp | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 2509b4bf3..41a2b1dce 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -184,7 +184,6 @@ struct LoadBaton { v8::Persistent callback; std::string file_name; std::string error_name; - bool error; std::vector faces; uv_work_t request; LoadBaton() : @@ -222,7 +221,6 @@ void LoadAsync(uv_work_t* req) { FT_Library library = nullptr; FT_Error error = FT_Init_FreeType(&library); if (error) { - baton->error = true; baton->error_name = std::string("could not open FreeType library"); return; } @@ -264,29 +262,15 @@ void AfterLoad(uv_work_t* req) { NanScope(); LoadBaton* baton = static_cast(req->data); - const unsigned argc = 1; - - v8::TryCatch try_catch; - v8::Local ctx = NanGetCurrentContext(); - - if (baton->error) { - v8::Local argv[argc] = { - v8::Exception::Error(NanNew(baton->error_name.c_str())) - }; - baton->callback->Call(ctx->Global(), argc, argv); + if (!baton->error_name.empty()) { + v8::Local argv[1] = { NanError(baton->error_name.c_str()) }; + NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); } else { - v8::Local argv[argc] = { - NanNull() - }; - baton->callback->Call(ctx->Global(), argc, argv); + v8::Local argv[2] = { NanNull(), NanNull() /* todo return custom object */ }; + NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv); } - if (try_catch.HasCaught()) { - node::FatalException(try_catch); - } - - baton->callback.Dispose(); - + NanDisposePersistent(baton->callback); delete baton; }; From b564fba4de6f3b831e325aeaaaf33d289d7d4676 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:33:44 -0700 Subject: [PATCH 08/41] prep for handling multiface fonts + call FT_Done_Freetype --- src/glyphs.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 41a2b1dce..7a98a4bc1 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -224,17 +224,20 @@ void LoadAsync(uv_work_t* req) { baton->error_name = std::string("could not open FreeType library"); return; } - std::vector faces; - FaceMetadata face; - std::vector points; - - FT_Face ft_face = nullptr; - - FT_New_Face(library, baton->file_name.c_str(), 0, &ft_face); - - for ( int i = 0; ft_face == 0 || i < ft_face->num_faces; ++i ) + 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 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); @@ -242,6 +245,7 @@ void LoadAsync(uv_work_t* req) { 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()); @@ -251,11 +255,13 @@ void LoadAsync(uv_work_t* req) { 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); - FT_Done_Face(ft_face); }; void AfterLoad(uv_work_t* req) { From b6f8ab1743b727a7e463d9799e8624b5659b1a52 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Wed, 18 Mar 2015 23:49:15 -0700 Subject: [PATCH 09/41] Start passing back FaceMetadata --- src/glyphs.cpp | 20 ++++++++++++++++---- test/test.js | 5 +++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 7a98a4bc1..d13234691 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -224,7 +224,6 @@ void LoadAsync(uv_work_t* req) { baton->error_name = std::string("could not open FreeType library"); return; } - std::vector faces; FT_Face ft_face = 0; int num_faces = 0; for ( int i = 0; ft_face == 0 || i < num_faces; ++i ) @@ -254,12 +253,11 @@ void LoadAsync(uv_work_t* req) { face.family_name = ft_face->family_name; face.style_name = ft_face->style_name; - faces.push_back(std::move(face)); + baton->faces.push_back(std::move(face)); if (ft_face) { FT_Done_Face(ft_face); } } - baton->faces = std::move(faces); FT_Done_FreeType(library); }; @@ -272,7 +270,21 @@ void AfterLoad(uv_work_t* req) { v8::Local argv[1] = { NanError(baton->error_name.c_str()) }; NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); } else { - v8::Local argv[2] = { NanNull(), NanNull() /* todo return custom object */ }; + v8::Local js_faces = NanNew(); + unsigned idx = 0; + for (auto const& face : baton->faces) { + v8::Local js_face = NanNew(); + js_face->Set(NanNew("family_name"),NanNew(face.family_name)); + js_face->Set(NanNew("style_name"),NanNew(face.style_name)); + v8::Local js_points = NanNew(face.points.size()); + unsigned p_idx = 0; + for (auto const& pt : face.points) { + js_points->Set(p_idx++,NanNew(pt)); + } + js_face->Set(NanNew("points"),js_points); + js_faces->Set(idx++,js_face); + } + v8::Local argv[2] = { NanNull(), js_faces }; NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv); } diff --git a/test/test.js b/test/test.js index 47470f5d9..88e8f5961 100644 --- a/test/test.js +++ b/test/test.js @@ -184,13 +184,14 @@ var UPDATE = process.env.UPDATE; // }); // }); +var expected = [{"family_name":"Fira Sans","style_name":"Medium","points":[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,402,508,509,510,511,536,537,538,539,567,700,710,711,728,729,730,731,732,733,768,769,770,771,772,774,775,776,778,779,780,787,788,806,807,900,901,902,904,905,906,908,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1122,1123,1138,1139,1140,1141,1168,1169,1170,1171,1174,1175,1176,1177,1178,1179,1180,1181,1184,1185,1186,1187,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1206,1207,1208,1209,1210,1211,1216,1217,1218,1227,1228,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1308,1309,1316,1317,1318,1319,7808,7809,7810,7811,7812,7813,7922,7923,8048,8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8112,8113,8118,8120,8121,8122,8123,8128,8134,8136,8137,8138,8139,8144,8145,8146,8147,8150,8151,8152,8153,8154,8155,8160,8161,8162,8163,8166,8167,8168,8169,8170,8171,8182,8184,8185,8186,8187,8199,8200,8203,8204,8205,8206,8207,8210,8211,8212,8213,8216,8217,8218,8220,8221,8222,8224,8225,8226,8230,8240,8249,8250,8260,8304,8308,8309,8310,8311,8312,8313,8314,8315,8316,8317,8318,8320,8321,8322,8323,8324,8325,8326,8327,8328,8329,8330,8331,8332,8333,8334,8364,8470,8482,8486,8494,8531,8532,8533,8534,8535,8536,8537,8538,8539,8540,8541,8542,8543,8592,8593,8594,8595,8596,8597,8598,8599,8600,8601,8678,8679,8680,8681,8682,8706,8709,8710,8719,8721,8722,8725,8729,8730,8734,8747,8776,8800,8804,8805,8901,8998,8999,9000,9003,9166,9647,9674,10145,11013,11014,11015,57344,57345,57346,57347,64257,64258,65279,127760]}]; describe('load', function() { it('loads', function(done) { var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'); assert.ok(fs.existsSync(firasans)); - fontnik.load(firasans, function(err, res) { - console.log(err, res); + fontnik.load(firasans, function(err, faces) { assert.ifError(err); + assert.deepEqual(faces,expected); done(); }); }); From 0bfaf034ee13ca3bc1850f348a2e1f3c03489206 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 00:01:17 -0700 Subject: [PATCH 10/41] use emplace_back to construct objects in place --- src/glyphs.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/glyphs.cpp b/src/glyphs.cpp index d13234691..300bc606f 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -174,10 +174,12 @@ struct FaceMetadata { std::string family_name; std::string style_name; std::vector points; - FaceMetadata() : - family_name(), - style_name(), - points() {} + FaceMetadata(std::string const& _family_name, + std::string const& _style_name, + std::vector && _points) : + family_name(_family_name), + style_name(_style_name), + points(std::move(_points)) {} }; struct LoadBaton { @@ -233,7 +235,6 @@ void LoadAsync(uv_work_t* req) { baton->error_name = std::string("could not open Face") + baton->file_name; return; } - FaceMetadata face; std::vector points; if (num_faces == 0) num_faces = ft_face->num_faces; @@ -242,18 +243,13 @@ void LoadAsync(uv_work_t* req) { 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); + if (charcode != 0) points.emplace_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; - - baton->faces.push_back(std::move(face)); + baton->faces.emplace_back(ft_face->family_name,ft_face->style_name,std::move(points)); if (ft_face) { FT_Done_Face(ft_face); } From 6b62495f916d70806bb18d127ade25d9d8549cf8 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 11:07:49 -0700 Subject: [PATCH 11/41] node v0.12.x support --- package.json | 2 +- src/glyphs.cpp | 36 +++++++++++------------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 38951225f..56ac27beb 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ ], "dependencies": { "minimist": "^0.2.0", - "nan": "^1.2.0", + "nan": "^1.7.0", "node-pre-gyp": "~0.6.4", "queue-async": "^1.0.7" }, diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 300bc606f..64b76095b 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -58,7 +58,8 @@ void Glyphs::Init(v8::Handle target) { // This has to be last, otherwise the properties won't show up on the // object in JavaScript. - target->Set(name, constructor->GetFunction()); + target->Set(name, tpl->GetFunction()); + NanAssignPersistent(constructor, tpl); } NAN_METHOD(Glyphs::New) { @@ -87,7 +88,7 @@ NAN_METHOD(Glyphs::New) { bool Glyphs::HasInstance(v8::Handle val) { if (!val->IsObject()) return false; - return constructor->HasInstance(val->ToObject()); + return NanNew(constructor)->HasInstance(val->ToObject()); } NAN_METHOD(Glyphs::Serialize) { @@ -130,7 +131,7 @@ NAN_METHOD(Glyphs::Range) { Glyphs *glyphs = node::ObjectWrap::Unwrap(args.This()); RangeBaton* baton = new RangeBaton(); - baton->callback = v8::Persistent::New(callback); + NanAssignPersistent(baton->callback, callback.As()); baton->glyphs = glyphs; baton->fontstack = *fontstack; baton->range = *range; @@ -158,7 +159,7 @@ NAN_METHOD(Glyphs::Codepoints) { try { std::vector points = fontnik::Glyphs::Codepoints(from); - v8::Handle result = v8::Array::New(points.size()); + v8::Handle result = NanNew(points.size()); for (size_t i = 0; i < points.size(); i++) { result->Set(i, NanNew(points[i])); @@ -304,31 +305,16 @@ void Glyphs::RangeAfter(uv_work_t* req) { NanScope(); RangeBaton* baton = static_cast(req->data); - const unsigned argc = 1; - - v8::TryCatch try_catch; - v8::Local ctx = NanGetCurrentContext(); - - if (baton->error) { - v8::Local argv[argc] = { - v8::Exception::Error(NanNew(baton->error_name.c_str())) - }; - baton->callback->Call(ctx->Global(), argc, argv); + if (!baton->error_name.empty()) { + v8::Local argv[1] = { NanError(baton->error_name.c_str()) }; + NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); } else { - v8::Local argv[argc] = { - NanNull() - }; - baton->callback->Call(ctx->Global(), argc, argv); - } - - if (try_catch.HasCaught()) { - node::FatalException(try_catch); + v8::Local argv[1] = { NanNull() }; + NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); } - baton->callback.Dispose(); - + NanDisposePersistent(baton->callback); delete baton; - delete req; } } // ns node_fontnik From f177796097bddb7a8032f2c8b1028c22fbb1b594 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 11:10:42 -0700 Subject: [PATCH 12/41] test on node v0.12.x + tweak travis script for simplicity --- .travis.yml | 35 +++++++++++++++++++++++++---------- deps/nvm_install.sh | 11 ----------- 2 files changed, 25 insertions(+), 21 deletions(-) delete mode 100755 deps/nvm_install.sh diff --git a/.travis.yml b/.travis.yml index baad82dec..1f4abb769 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,44 @@ language: cpp + os: - linux - osx + compiler: - clang - gcc + env: matrix: - - NODE_NVM_VERSION="0.8.26" - - NODE_NVM_VERSION="0.10" + - NODE_VERSION="0.10.37" + - NODE_VERSION="0.12.0" global: - BUILD: '/tmp/fontnik-build' - PKG_CONFIG_PATH: '/tmp/fontnik-build/lib/pkgconfig' - secure: "XV0lekmfgT+D9t0ZTIU+UJF6g+p3cBQMO6T6C9lkoKTC0YbtLtxSFtBahD/4PjL86DMJgTaf1nBmxqOxbrfkcpJUxnLe3r8u4Z2L/+7+QSACLNktlIfWNSO+33WxKNb4mVw6jMFZIo4ZurF016MXYzLzjpxRELW2oO2STUs2m44=" - secure: "CQNHbxw8yHlAdUVbKokHzHmj7C+duXP3mifWOkZm9GKw4myWsRFhhoSYZmOSkgj9EWfYYkedrqEr9+GaMg9rkVJuO/7jzn6S+M7CFXKJju6MoZEDO6WcFva4M8pw6IFb9q22GcQ+OsE8/i0DwchTokyFkNb3fpwWuwROUPQ/nWg=" + before_install: -- export platform=$(uname -s | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/") -- export PATH="$BUILD/bin:$PATH" -- if [[ "$platform" == "linux" ]]; then export CXX=g++-4.8; export CC=gcc-4.8; fi -- ./deps/nvm_install.sh -- ./deps/travis_build.sh + - export platform=$(uname -s | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/") + # here we set up the node version on the fly based on the matrix value. + # This is done manually so that it is easy to flip the 'language' to + # objective-c in another branch (to run the same travis.yml on OS X) + - git clone https://github.com/creationix/nvm.git ../.nvm + - source ../.nvm/nvm.sh + - nvm install $NODE_VERSION + - nvm use $NODE_VERSION + - node --version + - npm --version + - export PATH="$BUILD/bin:$PATH" + - if [[ "$platform" == "linux" ]]; then export CXX=g++-4.8; export CC=gcc-4.8; fi + - ./deps/travis_build.sh + install: -- npm install --build-from-source + - npm install --build-from-source + script: -- npm test + - npm test + after_success: -- ./deps/travis_publish.sh + - ./deps/travis_publish.sh diff --git a/deps/nvm_install.sh b/deps/nvm_install.sh deleted file mode 100755 index 72dbe553f..000000000 --- a/deps/nvm_install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -git clone https://github.com/creationix/nvm.git ../.nvm -source ../.nvm/nvm.sh -nvm install $NODE_NVM_VERSION -nvm use $NODE_NVM_VERSION -node --version -npm --version From ac6f71fda7971ce5218efe8daae19148599c4e78 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 17:08:53 -0700 Subject: [PATCH 13/41] code coverage --- .travis.yml | 57 +++++++++++++++++++++++++++++++----------- deps/travis_publish.sh | 2 +- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f4abb769..eb6aa7bc6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,36 @@ language: cpp -os: -- linux -- osx - -compiler: -- clang -- gcc +matrix: + include: + # Coverage + - os: osx + compiler: clang + env: NODE_VERSION="0.10" COVERAGE=true + # Linux + - os: linux + compiler: clang + env: NODE_VERSION="0.10" + - os: linux + compiler: clang + env: NODE_VERSION="0.12" + # OS X + - os: osx + compiler: clang + env: NODE_VERSION="0.10" + - os: osx + compiler: clang + env: NODE_VERSION="0.12" env: - matrix: - - NODE_VERSION="0.10.37" - - NODE_VERSION="0.12.0" global: - - BUILD: '/tmp/fontnik-build' - - PKG_CONFIG_PATH: '/tmp/fontnik-build/lib/pkgconfig' - - secure: "XV0lekmfgT+D9t0ZTIU+UJF6g+p3cBQMO6T6C9lkoKTC0YbtLtxSFtBahD/4PjL86DMJgTaf1nBmxqOxbrfkcpJUxnLe3r8u4Z2L/+7+QSACLNktlIfWNSO+33WxKNb4mVw6jMFZIo4ZurF016MXYzLzjpxRELW2oO2STUs2m44=" - - secure: "CQNHbxw8yHlAdUVbKokHzHmj7C+duXP3mifWOkZm9GKw4myWsRFhhoSYZmOSkgj9EWfYYkedrqEr9+GaMg9rkVJuO/7jzn6S+M7CFXKJju6MoZEDO6WcFva4M8pw6IFb9q22GcQ+OsE8/i0DwchTokyFkNb3fpwWuwROUPQ/nWg=" + - JOBS: "8" + - BUILD: '/tmp/fontnik-build' + - PKG_CONFIG_PATH: '/tmp/fontnik-build/lib/pkgconfig' + - secure: "XV0lekmfgT+D9t0ZTIU+UJF6g+p3cBQMO6T6C9lkoKTC0YbtLtxSFtBahD/4PjL86DMJgTaf1nBmxqOxbrfkcpJUxnLe3r8u4Z2L/+7+QSACLNktlIfWNSO+33WxKNb4mVw6jMFZIo4ZurF016MXYzLzjpxRELW2oO2STUs2m44=" + - secure: "CQNHbxw8yHlAdUVbKokHzHmj7C+duXP3mifWOkZm9GKw4myWsRFhhoSYZmOSkgj9EWfYYkedrqEr9+GaMg9rkVJuO/7jzn6S+M7CFXKJju6MoZEDO6WcFva4M8pw6IFb9q22GcQ+OsE8/i0DwchTokyFkNb3fpwWuwROUPQ/nWg=" before_install: + - export COVERAGE=${COVERAGE:-false} - export platform=$(uname -s | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/") # here we set up the node version on the fly based on the matrix value. # This is done manually so that it is easy to flip the 'language' to @@ -29,15 +41,30 @@ before_install: - nvm use $NODE_VERSION - node --version - npm --version + - if [[ ${COVERAGE} == true ]]; then + brew update; + brew install pyenv; + eval "$(pyenv init -)"; + pyenv install 2.7.6; + pyenv global 2.7.6; + pyenv rehash; + pip install cpp-coveralls; + pyenv rehash; + fi; - export PATH="$BUILD/bin:$PATH" - if [[ "$platform" == "linux" ]]; then export CXX=g++-4.8; export CC=gcc-4.8; fi - ./deps/travis_build.sh install: - - npm install --build-from-source + - if [[ ${COVERAGE} == true ]]; then + export LDFLAGS="--coverage" && export CXXFLAGS="--coverage" && npm install --build-from-source --debug --clang=1; + else + npm install --build-from-source --clang=1; + fi; script: - npm test + - if [[ ${COVERAGE} == true ]]; then cpp-coveralls --exclude tests --build-root build --gcov-options '\-lp' --exclude doc --exclude build/Release/obj/gen; fi; after_success: - ./deps/travis_publish.sh diff --git a/deps/travis_publish.sh b/deps/travis_publish.sh index 68c179708..cfba9da63 100755 --- a/deps/travis_publish.sh +++ b/deps/travis_publish.sh @@ -11,7 +11,7 @@ fi COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') -if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE" && [[ ${COVERAGE} == false ]]; then npm install aws-sdk From c173e1df2f22ea9967f88877ae52adca820559f4 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 17:47:03 -0700 Subject: [PATCH 14/41] coverage: ignore node_modules and tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eb6aa7bc6..d8087e531 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,7 +64,7 @@ install: script: - npm test - - if [[ ${COVERAGE} == true ]]; then cpp-coveralls --exclude tests --build-root build --gcov-options '\-lp' --exclude doc --exclude build/Release/obj/gen; fi; + - if [[ ${COVERAGE} == true ]]; then cpp-coveralls --exclude node_modules --exclude tests --build-root build --gcov-options '\-lp' --exclude doc --exclude build/Release/obj/gen; fi; after_success: - ./deps/travis_publish.sh From 76d1e0e6c28d9a90cec2f44b69f25a32bae3aac7 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 19 Mar 2015 17:48:36 -0700 Subject: [PATCH 15/41] add coveralls badge [skip ci] --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c63983b60..6ab080c80 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # node-fontnik -[![NPM](https://nodei.co/npm/fontnik.png?compact=true)](https://nodei.co/npm/fontnik/) [![Build Status](https://travis-ci.org/mapbox/node-fontnik.svg?branch=master)](https://travis-ci.org/mapbox/node-fontnik) +[![NPM](https://nodei.co/npm/fontnik.png?compact=true)](https://nodei.co/npm/fontnik/) +[![Build Status](https://travis-ci.org/mapbox/node-fontnik.svg?branch=master)](https://travis-ci.org/mapbox/node-fontnik) +[![Coverage Status](https://coveralls.io/repos/mapbox/node-fontnik/badge.svg?branch=coverage)](https://coveralls.io/r/mapbox/node-fontnik?branch=coverage) A library that delivers a range of glyphs rendered as SDFs (signed distance fields) in a protocol buffer. We use these encoded glyphs as the basic blocks of font rendering in [Mapbox GL](https://github.com/mapbox/mapbox-gl-js). SDF encoding is superior to traditional fonts for our usecase terms of scaling, rotation, and quickly deriving halos - WebGL doesn't have built-in font rendering, so the decision is between vectorization, which tends to be slow, and SDF generation. From 937d1482956402fda3f2dbb4001e4963c1dbb1e7 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Thu, 19 Mar 2015 17:58:31 -0700 Subject: [PATCH 16/41] delete delete delete --- binding.gyp | 10 - include/node_fontnik/glyphs.hpp | 74 +- index.js | 37 +- src/glyphs.cpp | 648 +++++++++++++----- src/node_fontnik.cpp | 9 +- test/expected/chars.json | 26 - test/magic.pbf | Bin 0 -> 76679 bytes test/test.js | 274 +++----- vendor/fontnik/COPYING | 502 -------------- vendor/fontnik/include/fontnik/face.hpp | 94 --- vendor/fontnik/include/fontnik/glyphs.hpp | 56 -- vendor/fontnik/src/face.cpp | 377 ---------- vendor/fontnik/src/glyphs.cpp | 174 ----- vendor/mapnik/COPYING | 502 -------------- vendor/mapnik/README.fontserver | 25 - vendor/mapnik/include/mapnik/config.hpp | 55 -- vendor/mapnik/include/mapnik/debug.hpp | 330 --------- .../include/mapnik/font_engine_freetype.hpp | 112 --- vendor/mapnik/include/mapnik/font_set.hpp | 59 -- vendor/mapnik/include/mapnik/noncopyable.hpp | 45 -- .../mapnik/include/mapnik/pixel_position.hpp | 94 --- .../text/evaluated_format_properties_ptr.hpp | 34 - .../mapnik/include/mapnik/text/face_set.hpp | 60 -- .../mapnik/include/mapnik/text/glyph_info.hpp | 88 --- vendor/mapnik/include/mapnik/unique_lock.hpp | 33 - vendor/mapnik/include/mapnik/util/fs.hpp | 47 -- vendor/mapnik/include/mapnik/utils.hpp | 170 ----- vendor/mapnik/src/debug.cpp | 164 ----- vendor/mapnik/src/font_engine_freetype.cpp | 431 ------------ vendor/mapnik/src/font_set.cpp | 90 --- vendor/mapnik/src/fs.cpp | 162 ----- vendor/mapnik/src/text/face_set.cpp | 50 -- vendor/node_mapnik/LICENSE.txt | 28 - vendor/node_mapnik/README.fontserver | 9 - vendor/node_mapnik/include/mapnik_fonts.hpp | 106 --- 35 files changed, 626 insertions(+), 4349 deletions(-) delete mode 100644 test/expected/chars.json create mode 100644 test/magic.pbf delete mode 100644 vendor/fontnik/COPYING delete mode 100644 vendor/fontnik/include/fontnik/face.hpp delete mode 100644 vendor/fontnik/include/fontnik/glyphs.hpp delete mode 100644 vendor/fontnik/src/face.cpp delete mode 100644 vendor/fontnik/src/glyphs.cpp delete mode 100644 vendor/mapnik/COPYING delete mode 100644 vendor/mapnik/README.fontserver delete mode 100644 vendor/mapnik/include/mapnik/config.hpp delete mode 100644 vendor/mapnik/include/mapnik/debug.hpp delete mode 100644 vendor/mapnik/include/mapnik/font_engine_freetype.hpp delete mode 100644 vendor/mapnik/include/mapnik/font_set.hpp delete mode 100644 vendor/mapnik/include/mapnik/noncopyable.hpp delete mode 100644 vendor/mapnik/include/mapnik/pixel_position.hpp delete mode 100644 vendor/mapnik/include/mapnik/text/evaluated_format_properties_ptr.hpp delete mode 100644 vendor/mapnik/include/mapnik/text/face_set.hpp delete mode 100644 vendor/mapnik/include/mapnik/text/glyph_info.hpp delete mode 100644 vendor/mapnik/include/mapnik/unique_lock.hpp delete mode 100644 vendor/mapnik/include/mapnik/util/fs.hpp delete mode 100644 vendor/mapnik/include/mapnik/utils.hpp delete mode 100644 vendor/mapnik/src/debug.cpp delete mode 100644 vendor/mapnik/src/font_engine_freetype.cpp delete mode 100644 vendor/mapnik/src/font_set.cpp delete mode 100644 vendor/mapnik/src/fs.cpp delete mode 100644 vendor/mapnik/src/text/face_set.cpp delete mode 100644 vendor/node_mapnik/LICENSE.txt delete mode 100644 vendor/node_mapnik/README.fontserver delete mode 100644 vendor/node_mapnik/include/mapnik_fonts.hpp diff --git a/binding.gyp b/binding.gyp index 44742ecc9..5abb98af1 100644 --- a/binding.gyp +++ b/binding.gyp @@ -24,21 +24,11 @@ 'src/node_fontnik.cpp', 'src/glyphs.cpp', 'vendor/agg/src/agg_curves.cpp', - 'vendor/fontnik/src/glyphs.cpp', - 'vendor/fontnik/src/face.cpp', - 'vendor/mapnik/src/debug.cpp', - 'vendor/mapnik/src/font_engine_freetype.cpp', - 'vendor/mapnik/src/font_set.cpp', - 'vendor/mapnik/src/fs.cpp', - 'vendor/mapnik/src/text/face_set.cpp', '<(SHARED_INTERMEDIATE_DIR)/glyphs.pb.cc' ], 'include_dirs': [ './include', './vendor/agg/include', - './vendor/fontnik/include', - './vendor/mapnik/include', - './vendor/node_mapnik/include', '<(SHARED_INTERMEDIATE_DIR)/', ' +#include "glyphs.pb.h" #include #include -namespace node_fontnik +// freetype2 +extern "C" { +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +} -class Glyphs : public node::ObjectWrap { -public: - static v8::Persistent constructor; - static void Init(v8::Handle target); - static bool HasInstance(v8::Handle val); -protected: - Glyphs(); - Glyphs(const char *data, size_t length); - ~Glyphs(); - - static NAN_METHOD(New); - static NAN_METHOD(Range); - static void AsyncRange(uv_work_t* req); - static void RangeAfter(uv_work_t* req); - static NAN_METHOD(Serialize); - static NAN_METHOD(Codepoints); - - fontnik::Glyphs glyphs; -}; +namespace node_fontnik +{ NAN_METHOD(Load); void LoadAsync(uv_work_t* req); void AfterLoad(uv_work_t* req); +NAN_METHOD(Range); +void RangeAsync(uv_work_t* req); +void AfterRange(uv_work_t* req); +struct glyph_info; +void RenderSDF(glyph_info &glyph, + int size, + int buffer, + float cutoff, + FT_Face ft_face); + +struct glyph_info +{ + glyph_info() + : glyph_index(0), + // face(nullptr), + bitmap(""), + char_index(0), + left(0), + top(0), + width(0), + height(0), + advance(0.0), + line_height(0.0), + ascender(0.0), + descender(0.0) {} + unsigned glyph_index; + // fontnik::face_ptr face; + std::string bitmap; + // Position in the string of all characters i.e. before itemizing + unsigned char_index; + int32_t left; + int32_t top; + uint32_t width; + uint32_t height; + double advance; + // Line height returned by FreeType, includes normal font + // line spacing, but not additional user defined spacing + double line_height; + // Ascender and descender from baseline returned by FreeType + double ascender; + double descender; +}; + } // ns node_fontnik diff --git a/index.js b/index.js index 46232311a..8dfd15c81 100644 --- a/index.js +++ b/index.js @@ -1,36 +1 @@ -var zlib = require('zlib'); -var path = require('path'); -var util = require('util'); -var fontnik = require('./lib/fontnik.node'); - -module.exports = fontnik; -module.exports.range = range; -module.exports.getRange = getRange; - -// Retrieve a range of glyphs as a pbf. -function range(options, callback) { - 'use strict'; - options = options || {}; - options.fontstack = options.fontstack || 'Open Sans Regular'; - - var glyphs = new fontnik.Glyphs(); - glyphs.range(options.fontstack, options.start + '-' + options.end, getRange(options.start, options.end), gzip); - - function gzip(err) { - if (err) return callback(err); - var after = glyphs.serialize(); - zlib.gzip(after, callback); - } -} - -function getRange(start, end) { - if (typeof start !== 'number') throw new Error('start must be a number from 0-65535'); - if (start < 0) throw new Error('start must be a number from 0-65535'); - if (typeof end !== 'number') throw new Error('end must be a number from 0-65535'); - if (end > 65535) throw new Error('end must be a number from 0-65535'); - if (start > end) throw new Error('start must be less than or equal to end'); - var range = []; - for (var i = start; i <= end; i++) range.push(i); - return range; -} - +module.exports = require('./lib/fontnik.node'); diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 64b76095b..8a0f72423 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -5,172 +5,33 @@ #include #include -// freetype2 -extern "C" -{ -#include -#include FT_FREETYPE_H -#include FT_GLYPH_H -#include FT_OUTLINE_H -} +#include "agg_curves.h" + +// boost +// undef B0 to workaround https://svn.boost.org/trac/boost/ticket/10467 +#undef B0 +#include +#include +#include +#include + +// stl +#include + +namespace bg = boost::geometry; +namespace bgm = bg::model; +namespace bgi = bg::index; +typedef bgm::point Point; +typedef bgm::box Box; +typedef std::vector Points; +typedef std::vector Rings; +typedef std::pair SegmentPair; +typedef std::pair SegmentValue; +typedef bgi::rtree> Tree; namespace node_fontnik { -struct RangeBaton { - v8::Persistent callback; - Glyphs *glyphs; - std::string fontstack; - std::string range; - std::vector chars; - bool error; - std::string error_name; -}; - -v8::Persistent 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 target) { - NanScope(); - - v8::Local tpl = NanNew(New); - v8::Local name = NanNew("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, tpl->GetFunction()); - NanAssignPersistent(constructor, tpl); -} - -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 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 val) { - if (!val->IsObject()) return false; - return NanNew(constructor)->HasInstance(val->ToObject()); -} - -NAN_METHOD(Glyphs::Serialize) { - NanScope(); - std::string serialized = node::ObjectWrap::Unwrap(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 charsArray = v8::Local::Cast(args[2]); - v8::Local callback = v8::Local::Cast(args[3]); - - unsigned array_size = charsArray->Length(); - std::vector chars; - for (unsigned i=0; i < array_size; i++) { - chars.push_back(charsArray->Get(i)->IntegerValue()); - } - - Glyphs *glyphs = node::ObjectWrap::Unwrap(args.This()); - - RangeBaton* baton = new RangeBaton(); - NanAssignPersistent(baton->callback, callback.As()); - 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 points = fontnik::Glyphs::Codepoints(from); - - v8::Handle result = NanNew(points.size()); - - for (size_t i = 0; i < points.size(); i++) { - result->Set(i, NanNew(points[i])); - } - NanReturnValue(result); - } catch (std::exception const& ex) { - return NanThrowTypeError(ex.what()); - } - NanReturnUndefined(); -} - struct FaceMetadata { std::string family_name; std::string style_name; @@ -195,6 +56,24 @@ struct LoadBaton { faces() {} }; +struct RangeBaton { + v8::Persistent callback; + std::string file_name; + std::string error_name; + std::uint32_t start; + std::uint32_t end; + std::vector chars; + std::string message; + uv_work_t request; + RangeBaton() : + file_name(), + error_name(), + start(), + end(), + chars(), + message() {} +}; + NAN_METHOD(Load) { NanScope(); @@ -203,7 +82,7 @@ NAN_METHOD(Load) { 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"); + return NanThrowTypeError("Callback must be a function"); } v8::Local callback = args[1].As(); @@ -218,6 +97,50 @@ NAN_METHOD(Load) { NanReturnUndefined(); } +NAN_METHOD(Range) { + NanScope(); + + // Validate arguments. + if (args.Length() < 1 || !args[0]->IsString()) { + return NanThrowTypeError("First argument must be a path to a font"); + } + + if (args.Length() < 2 || !args[1]->IsNumber() || args[1]->IntegerValue() < 0) { + return NanThrowTypeError("Second argument 'start' must be a number from 0-65535"); + } + + if (args.Length() < 3 || !args[2]->IsNumber() || args[2]->IntegerValue() > 65535) { + return NanThrowTypeError("Third argument 'end' must be a number from 0-65535"); + } + + if (args[2]->IntegerValue() < args[1]->IntegerValue()) { + return NanThrowTypeError("Start must be less than or equal to end"); + } + + if (args.Length() < 4 || !args[3]->IsFunction()) { + return NanThrowTypeError("Callback must be a function"); + } + + v8::Local callback = args[3].As(); + + RangeBaton* baton = new RangeBaton(); + baton->file_name = *NanUtf8String(args[0]); + + baton->start = args[1]->IntegerValue(); + baton->end = args[2]->IntegerValue(); + + 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()); + + uv_queue_work(uv_default_loop(), &baton->request, RangeAsync, (uv_after_work_cb)AfterRange); + NanReturnUndefined(); +} + void LoadAsync(uv_work_t* req) { LoadBaton* baton = static_cast(req->data); @@ -250,13 +173,12 @@ void LoadAsync(uv_work_t* req) { std::sort(points.begin(), points.end()); auto last = std::unique(points.begin(), points.end()); points.erase(last, points.end()); - baton->faces.emplace_back(ft_face->family_name,ft_face->style_name,std::move(points)); + baton->faces.emplace_back(ft_face->family_name, ft_face->style_name, std::move(points)); if (ft_face) { FT_Done_Face(ft_face); } } FT_Done_FreeType(library); - }; void AfterLoad(uv_work_t* req) { @@ -289,19 +211,76 @@ void AfterLoad(uv_work_t* req) { delete baton; }; -void Glyphs::AsyncRange(uv_work_t* req) { +void RangeAsync(uv_work_t* req) { RangeBaton* baton = static_cast(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(); + FT_Library library = nullptr; + FT_Error error = FT_Init_FreeType(&library); + if (error) { + baton->error_name = std::string("could not open FreeType library"); return; } + + FT_Face ft_face = 0; + + llmr::glyphs::glyphs glyphs; + + 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; + } + + llmr::glyphs::fontstack *mutable_fontstack = glyphs.add_stacks(); + mutable_fontstack->set_name(std::string(ft_face->family_name) + " " + ft_face->style_name); + mutable_fontstack->set_range(std::to_string(baton->start) + "-" + std::to_string(baton->end)); + + const double scale_factor = 1.0; + + // Set character sizes. + double size = 24 * scale_factor; + FT_Set_Char_Size(ft_face,0,(FT_F26Dot6)(size * (1<<6)),0,0); + + for (std::vector::size_type i = 0; i != baton->chars.size(); i++) { + FT_ULong char_code = baton->chars[i]; + glyph_info glyph; + + // Get FreeType face from face_ptr. + FT_UInt char_index = FT_Get_Char_Index(ft_face, char_code); + + if (!char_index) continue; + + glyph.glyph_index = char_index; + RenderSDF(glyph, 24, 3, 0.25, ft_face); + + // Add glyph to fontstack. + llmr::glyphs::glyph *mutable_glyph = mutable_fontstack->add_glyphs(); + mutable_glyph->set_id(char_code); + mutable_glyph->set_width(glyph.width); + mutable_glyph->set_height(glyph.height); + mutable_glyph->set_left(glyph.left); + mutable_glyph->set_top(glyph.top - glyph.ascender); + mutable_glyph->set_advance(glyph.advance); + + if (glyph.width > 0) { + mutable_glyph->set_bitmap(glyph.bitmap); + } + + } + if (ft_face) { + FT_Done_Face(ft_face); + } + } + + baton->message = glyphs.SerializeAsString(); + + FT_Done_FreeType(library); } -void Glyphs::RangeAfter(uv_work_t* req) { +void AfterRange(uv_work_t* req) { NanScope(); RangeBaton* baton = static_cast(req->data); @@ -309,12 +288,333 @@ void Glyphs::RangeAfter(uv_work_t* req) { v8::Local argv[1] = { NanError(baton->error_name.c_str()) }; NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); } else { - v8::Local argv[1] = { NanNull() }; - NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 1, argv); + v8::Local js_faces = NanNew(); + unsigned idx = 0; + v8::Local argv[2] = { NanNull(), NanNewBufferHandle(baton->message.data(), baton->message.size()) }; + NanMakeCallback(NanGetCurrentContext()->Global(), NanNew(baton->callback), 2, argv); } NanDisposePersistent(baton->callback); delete baton; +}; + +struct User { + Rings rings; + Points ring; +}; + +void CloseRing(Points &ring) +{ + const Point &first = ring.front(); + const Point &last = ring.back(); + + if (first.get<0>() != last.get<0>() || + first.get<1>() != last.get<1>()) + { + ring.push_back(first); + } +} + +int MoveTo(const FT_Vector *to, void *ptr) +{ + User *user = (User*)ptr; + if (!user->ring.empty()) { + CloseRing(user->ring); + user->rings.push_back(user->ring); + user->ring.clear(); + } + user->ring.push_back(Point { float(to->x) / 64, float(to->y) / 64 }); + return 0; +} + +int LineTo(const FT_Vector *to, void *ptr) +{ + User *user = (User*)ptr; + user->ring.push_back(Point { float(to->x) / 64, float(to->y) / 64 }); + return 0; +} + +int ConicTo(const FT_Vector *control, + const FT_Vector *to, + void *ptr) +{ + User *user = (User*)ptr; + + Point prev = user->ring.back(); + + // pop off last point, duplicate of first point in bezier curve + user->ring.pop_back(); + + agg_fontnik::curve3_div curve(prev.get<0>(), prev.get<1>(), + float(control->x) / 64, float(control->y) / 64, + float(to->x) / 64, float(to->y) / 64); + + curve.rewind(0); + double x, y; + unsigned cmd; + + while (agg_fontnik::path_cmd_stop != (cmd = curve.vertex(&x, &y))) { + user->ring.push_back(Point {x, y}); + } + + return 0; +} + +int CubicTo(const FT_Vector *c1, + const FT_Vector *c2, + const FT_Vector *to, + void *ptr) +{ + User *user = (User*)ptr; + + Point prev = user->ring.back(); + + // pop off last point, duplicate of first point in bezier curve + user->ring.pop_back(); + + agg_fontnik::curve4_div curve(prev.get<0>(), prev.get<1>(), + float(c1->x) / 64, float(c1->y) / 64, + float(c2->x) / 64, float(c2->y) / 64, + float(to->x) / 64, float(to->y) / 64); + + curve.rewind(0); + double x, y; + unsigned cmd; + + while (agg_fontnik::path_cmd_stop != (cmd = curve.vertex(&x, &y))) { + user->ring.push_back(Point {x, y}); + } + + return 0; +} + +// point in polygon ray casting algorithm +bool PolyContainsPoint(const Rings &rings, const Point &p) +{ + bool c = false; + + for (const Points &ring : rings) { + auto p1 = ring.begin(); + auto p2 = p1 + 1; + + for (; p2 != ring.end(); p1++, p2++) { + if (((p1->get<1>() > p.get<1>()) != (p2->get<1>() > p.get<1>())) && (p.get<0>() < (p2->get<0>() - p1->get<0>()) * (p.get<1>() - p1->get<1>()) / (p2->get<1>() - p1->get<1>()) + p1->get<0>())) { + c = !c; + } + } + } + + return c; +} + +double SquaredDistance(const Point &v, const Point &w) +{ + const double a = v.get<0>() - w.get<0>(); + const double b = v.get<1>() - w.get<1>(); + return a * a + b * b; +} + +Point ProjectPointOnLineSegment(const Point &p, + const Point &v, + const Point &w) +{ + const double l2 = SquaredDistance(v, w); + if (l2 == 0) return v; + + const double t = ((p.get<0>() - v.get<0>()) * (w.get<0>() - v.get<0>()) + (p.get<1>() - v.get<1>()) * (w.get<1>() - v.get<1>())) / l2; + if (t < 0) return v; + if (t > 1) return w; + + return Point { + v.get<0>() + t * (w.get<0>() - v.get<0>()), + v.get<1>() + t * (w.get<1>() - v.get<1>()) + }; +} + +double SquaredDistanceToLineSegment(const Point &p, + const Point &v, + const Point &w) +{ + const Point s = ProjectPointOnLineSegment(p, v, w); + return SquaredDistance(p, s); +} + +double MinDistanceToLineSegment(const Tree &tree, + const Point &p, + int radius) +{ + const int squared_radius = radius * radius; + + std::vector results; + tree.query(bgi::intersects( + Box{ + Point{p.get<0>() - radius, p.get<1>() - radius}, + Point{p.get<0>() + radius, p.get<1>() + radius} + }), + std::back_inserter(results)); + + double sqaured_distance = std::numeric_limits::infinity(); + + for (const auto &value : results) { + const SegmentPair &segment = value.second; + const double dist = SquaredDistanceToLineSegment(p, + segment.first, + segment.second); + if (dist < sqaured_distance && dist < squared_radius) { + sqaured_distance = dist; + } + } + + return std::sqrt(sqaured_distance); +} + +void RenderSDF(glyph_info &glyph, + int size, + int buffer, + float cutoff, + FT_Face ft_face) +{ + // // Check if char is already in cache + // glyph_info_cache_type::const_iterator itr; + // itr = glyph_info_cache_.find(glyph.glyph_index); + // if (itr != glyph_info_cache_.end()) { + // glyph = itr->second; + // return; + // } + + if (FT_Load_Glyph (ft_face, glyph.glyph_index, FT_LOAD_NO_HINTING)) { + return; + } + + FT_Glyph ft_glyph; + if (FT_Get_Glyph(ft_face->glyph, &ft_glyph)) return; + + int advance = ft_face->glyph->metrics.horiAdvance / 64; + int ascender = ft_face->size->metrics.ascender / 64; + int descender = ft_face->size->metrics.descender / 64; + + glyph.line_height = ft_face->size->metrics.height; + glyph.advance = advance; + glyph.ascender = ascender; + glyph.descender = descender; + + FT_Outline_Funcs func_interface = { + .move_to = &MoveTo, + .line_to = &LineTo, + .conic_to = &ConicTo, + .cubic_to = &CubicTo, + .shift = 0, + .delta = 0 + }; + + User user; + + // Decompose outline into bezier curves and line segments + FT_Outline outline = ((FT_OutlineGlyph)ft_glyph)->outline; + if (FT_Outline_Decompose(&outline, &func_interface, &user)) return; + + if (!user.ring.empty()) { + CloseRing(user.ring); + user.rings.push_back(user.ring); + } + + if (user.rings.empty()) return; + + // Calculate the real glyph bbox. + double bbox_xmin = std::numeric_limits::infinity(), + bbox_ymin = std::numeric_limits::infinity(); + + double bbox_xmax = -std::numeric_limits::infinity(), + bbox_ymax = -std::numeric_limits::infinity(); + + for (const Points &ring : user.rings) { + for (const Point &point : ring) { + if (point.get<0>() > bbox_xmax) bbox_xmax = point.get<0>(); + if (point.get<0>() < bbox_xmin) bbox_xmin = point.get<0>(); + if (point.get<1>() > bbox_ymax) bbox_ymax = point.get<1>(); + if (point.get<1>() < bbox_ymin) bbox_ymin = point.get<1>(); + } + } + + bbox_xmin = std::round(bbox_xmin); + bbox_ymin = std::round(bbox_ymin); + bbox_xmax = std::round(bbox_xmax); + bbox_ymax = std::round(bbox_ymax); + + // Offset so that glyph outlines are in the bounding box. + for (Points &ring : user.rings) { + for (Point &point : ring) { + point.set<0>(point.get<0>() + -bbox_xmin + buffer); + point.set<1>(point.get<1>() + -bbox_ymin + buffer); + } + } + + if (bbox_xmax - bbox_xmin == 0 || bbox_ymax - bbox_ymin == 0) return; + + glyph.left = bbox_xmin; + glyph.top = bbox_ymax; + glyph.width = bbox_xmax - bbox_xmin; + glyph.height = bbox_ymax - bbox_ymin; + + Tree tree; + float offset = 0.5; + int radius = 8; + + for (const Points &ring : user.rings) { + auto p1 = ring.begin(); + auto p2 = p1 + 1; + + for (; p2 != ring.end(); p1++, p2++) { + const int segment_x1 = std::min(p1->get<0>(), p2->get<0>()); + const int segment_x2 = std::max(p1->get<0>(), p2->get<0>()); + const int segment_y1 = std::min(p1->get<1>(), p2->get<1>()); + const int segment_y2 = std::max(p1->get<1>(), p2->get<1>()); + + tree.insert(SegmentValue { + Box { + Point {segment_x1, segment_y1}, + Point {segment_x2, segment_y2} + }, + SegmentPair { + Point {p1->get<0>(), p1->get<1>()}, + Point {p2->get<0>(), p2->get<1>()} + } + }); + } + } + + // Loop over every pixel and determine the positive/negative distance to the outline. + unsigned int buffered_width = glyph.width + 2 * buffer; + unsigned int buffered_height = glyph.height + 2 * buffer; + unsigned int bitmap_size = buffered_width * buffered_height; + glyph.bitmap.resize(bitmap_size); + + for (unsigned int y = 0; y < buffered_height; y++) { + for (unsigned int x = 0; x < buffered_width; x++) { + unsigned int ypos = buffered_height - y - 1; + unsigned int i = ypos * buffered_width + x; + + double d = MinDistanceToLineSegment(tree, Point {x + offset, y + offset}, radius) * (256 / radius); + + // Invert if point is inside. + const bool inside = PolyContainsPoint(user.rings, Point { x + offset, y + offset }); + if (inside) { + d = -d; + } + + // Shift the 0 so that we can fit a few negative values + // into our 8 bits. + d += cutoff * 256; + + // Clamp to 0-255 to prevent overflows or underflows. + int n = d > 255 ? 255 : d; + n = n < 0 ? 0 : n; + + glyph.bitmap[i] = static_cast(255 - n); + } + } + + FT_Done_Glyph(ft_glyph); } } // ns node_fontnik diff --git a/src/node_fontnik.cpp b/src/node_fontnik.cpp index 1269f217e..522da5f2a 100644 --- a/src/node_fontnik.cpp +++ b/src/node_fontnik.cpp @@ -1,6 +1,3 @@ -// node-mapnik -#include "mapnik_fonts.hpp" - // fontnik #include @@ -12,13 +9,9 @@ namespace node_fontnik { void RegisterModule(v8::Handle target) { - NODE_SET_METHOD(target, "register_fonts", node_mapnik_fontnik::register_fonts); - NODE_SET_METHOD(target, "faces", node_mapnik_fontnik::available_font_faces); - NODE_SET_METHOD(target, "files", node_mapnik_fontnik::available_font_files); NODE_SET_METHOD(target, "load", node_fontnik::Load); - // NODE_SET_METHOD(target, "range", node_fontnik::Range); + NODE_SET_METHOD(target, "range", node_fontnik::Range); - Glyphs::Init(target); } NODE_MODULE(fontnik, RegisterModule); diff --git a/test/expected/chars.json b/test/expected/chars.json deleted file mode 100644 index d05ee74f0..000000000 --- a/test/expected/chars.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "stacks": { - "Open Sans Regular": { - "glyphs": { - "97": { - "id": 97, - "width": 10, - "height": 13, - "left": 1, - "top": -13, - "advance": 13 - }, - "122": { - "id": 122, - "width": 9, - "height": 13, - "left": 1, - "top": -13, - "advance": 11 - } - }, - "name": "Open Sans Regular", - "range": "a-and-z" - } - } -} \ No newline at end of file diff --git a/test/magic.pbf b/test/magic.pbf new file mode 100644 index 0000000000000000000000000000000000000000..c574b30b7672adc5cd83c3a59b4a7184d7ce3d30 GIT binary patch literal 76679 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 -N%b)RIdOU^@f5R5G5})WSF-fu+z)?i6#)nl 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 P%1d}*1nhGz_v?QE3I)-| literal 0 HcmV?d00001 diff --git a/test/test.js b/test/test.js index 88e8f5961..1845fd78e 100644 --- a/test/test.js +++ b/test/test.js @@ -8,186 +8,20 @@ var Protobuf = require('pbf'); var Glyphs = require('./format/glyphs'); var UPDATE = process.env.UPDATE; -// function nobuffer(key, val) { -// return key !== '_buffer' && key !== 'bitmap' ? val : undefined; -// } -// -// function jsonEqual(key, json) { -// if (UPDATE) fs.writeFileSync(__dirname + '/expected/'+key+'.json', JSON.stringify(json, null, 2)); -// assert.deepEqual(json, require('./expected/'+key+'.json')); -// } -// -// fontnik.register_fonts(path.resolve(__dirname + '/../fonts/'), { recurse: true }); -// -// describe('glyphs', function() { -// var data; -// before(function(done) { -// zlib.inflate(zdata, function(err, d) { -// assert.ifError(err); -// data = d; -// done(); -// }); -// }); -// -// it('serialize', function(done) { -// // On disk fixture generated with the following code. -// if (UPDATE) { -// fontnik.range({ -// fontstack:'Open Sans Regular', -// start: 0, -// end: 256 -// }, function(err, zdata) { -// if (err) throw err; -// fs.writeFileSync(__dirname + '/fixtures/range.0.256.pbf', zdata); -// done(); -// }); -// } -// var glyphs = new fontnik.Glyphs(data); -// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); -// var json = JSON.parse(JSON.stringify(vt, nobuffer)); -// jsonEqual('range', json); -// done(); -// }); -// -// it('range', function(done) { -// var glyphs = new fontnik.Glyphs(); -// glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), function(err) { -// assert.ifError(err); -// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); -// var json = JSON.parse(JSON.stringify(vt, nobuffer)); -// jsonEqual('range', json); -// done(); -// }); -// }); -// -// // Render a long range of characters which can cause segfaults -// // with V8 arrays ... not sure yet why. -// it('longrange', function(done) { -// var glyphs = new fontnik.Glyphs(); -// glyphs.range('Open Sans Regular', '0-1024', fontnik.getRange(0, 1024), function(err) { -// assert.ifError(err); -// done(); -// }); -// }); -// -// it('range (chars input)', function(done) { -// var glyphs = new fontnik.Glyphs(); -// glyphs.range('Open Sans Regular', 'a-and-z', [('a').charCodeAt(0), ('z').charCodeAt(0)], function(err) { -// assert.ifError(err); -// var vt = new Glyphs(new Protobuf(new Uint8Array(glyphs.serialize()))); -// var json = JSON.parse(JSON.stringify(vt, nobuffer)); -// jsonEqual('chars', json); -// done(); -// }); -// }); -// -// it('range typeerror fontstack', function(done) { -// var glyphs = new fontnik.Glyphs(); -// assert.throws(function() { -// glyphs.range(0, '0-256', fontnik.getRange(0, 256), function() {}); -// }, /fontstack must be a string/); -// done(); -// }); -// -// it('range typeerror range', function(done) { -// var glyphs = new fontnik.Glyphs(); -// assert.throws(function() { -// glyphs.range('Open Sans Regular', 0, fontnik.getRange(0, 256), function() {}); -// }, /range must be a string/); -// done(); -// }); -// -// it('range typeerror chars', function(done) { -// var glyphs = new fontnik.Glyphs(); -// assert.throws(function() { -// glyphs.range('Open Sans Regular', '0-256', 'foo', function() {}); -// }, /chars must be an array/); -// done(); -// }); -// -// it('range typeerror callback', function(done) { -// var glyphs = new fontnik.Glyphs(); -// assert.throws(function() { -// glyphs.range('Open Sans Regular', '0-256', fontnik.getRange(0, 256), ''); -// }, /callback must be a function/); -// done(); -// }); -// -// it('range for fontstack with 0 matching fonts', function(done) { -// var glyphs = new fontnik.Glyphs(); -// glyphs.range('doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { -// assert.ok(err); -// assert.equal("Error: Failed to find face 'doesnotexist' in font set 'doesnotexist'", err.toString()); -// done(); -// }); -// }); -// -// it('range for fontstack with 1 bad font', function(done) { -// var glyphs = new fontnik.Glyphs(); -// glyphs.range('Open Sans Regular, doesnotexist', '0-256', fontnik.getRange(0, 256), function(err) { -// assert.ok(err); -// assert.equal("Error: Failed to find face 'doesnotexist' in font set 'Open Sans Regular, doesnotexist'", err.toString()); -// done(); -// }); -// }); -// -// // Should error because start is < 0 -// it('getRange error start < 0', function() { -// assert.throws(function() { -// fontnik.getRange(-128, 256); -// }, 'Error: start must be a number from 0-65535'); -// }); -// -// // Should error because end < start -// it('getRange error end < start', function() { -// assert.throws(function() { -// fontnik.getRange(256, 0); -// }, 'Error: start must be less than or equal to end'); -// }); -// -// // Should error because end > 65535 -// it('getRange error end > 65535', function() { -// assert.throws(function() { -// fontnik.getRange(0, 65536); -// }, 'Error: end must be a number from 0-65535'); -// }); -// }); -// -// describe('codepoints', function() { -// it('basic scanning: open sans', function() { -// var glyphs = new fontnik.Glyphs(); -// var cp = glyphs.codepoints('Open Sans Regular'); -// assert.equal(cp.length, 882); -// }); -// it('basic scanning: fira sans', function() { -// var glyphs = new fontnik.Glyphs(); -// var cp = glyphs.codepoints('Fira Sans Medium'); -// assert.equal(cp.length, 789); -// }); -// it('basic scanning: fira sans + open sans', function() { -// var glyphs = new fontnik.Glyphs(); -// var cp = glyphs.codepoints('Fira Sans Medium, Open Sans Regular'); -// assert.equal(cp.length, 1021); -// }); -// it('invalid font face', function() { -// var glyphs = new fontnik.Glyphs(); -// assert.throws(function() { -// glyphs.codepoints('foo-bar-invalid'); -// }); -// }); -// }); -// -// describe('faces', function() { -// it('list faces', function() { -// var faces = fontnik.faces(); -// assert.deepEqual(faces, ['Fira Sans Medium', 'Open Sans Regular']); -// }); -// }); +function nobuffer(key, val) { + return key !== '_buffer' && key !== 'bitmap' ? val : undefined; +} + +function jsonEqual(key, json) { + if (UPDATE) fs.writeFileSync(__dirname + '/expected/'+key+'.json', JSON.stringify(json, null, 2)); + assert.deepEqual(json, require('./expected/'+key+'.json')); +} var expected = [{"family_name":"Fira Sans","style_name":"Medium","points":[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,402,508,509,510,511,536,537,538,539,567,700,710,711,728,729,730,731,732,733,768,769,770,771,772,774,775,776,778,779,780,787,788,806,807,900,901,902,904,905,906,908,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1122,1123,1138,1139,1140,1141,1168,1169,1170,1171,1174,1175,1176,1177,1178,1179,1180,1181,1184,1185,1186,1187,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1206,1207,1208,1209,1210,1211,1216,1217,1218,1227,1228,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1308,1309,1316,1317,1318,1319,7808,7809,7810,7811,7812,7813,7922,7923,8048,8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8112,8113,8118,8120,8121,8122,8123,8128,8134,8136,8137,8138,8139,8144,8145,8146,8147,8150,8151,8152,8153,8154,8155,8160,8161,8162,8163,8166,8167,8168,8169,8170,8171,8182,8184,8185,8186,8187,8199,8200,8203,8204,8205,8206,8207,8210,8211,8212,8213,8216,8217,8218,8220,8221,8222,8224,8225,8226,8230,8240,8249,8250,8260,8304,8308,8309,8310,8311,8312,8313,8314,8315,8316,8317,8318,8320,8321,8322,8323,8324,8325,8326,8327,8328,8329,8330,8331,8332,8333,8334,8364,8470,8482,8486,8494,8531,8532,8533,8534,8535,8536,8537,8538,8539,8540,8541,8542,8543,8592,8593,8594,8595,8596,8597,8598,8599,8600,8601,8678,8679,8680,8681,8682,8706,8709,8710,8719,8721,8722,8725,8729,8730,8734,8747,8776,8800,8804,8805,8901,8998,8999,9000,9003,9166,9647,9674,10145,11013,11014,11015,57344,57345,57346,57347,64257,64258,65279,127760]}]; +var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'); +var opensans = path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf'); describe('load', function() { - it('loads', function(done) { - var firasans = path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'); + it('loads: fira sans', function(done) { assert.ok(fs.existsSync(firasans)); fontnik.load(firasans, function(err, faces) { assert.ifError(err); @@ -195,4 +29,90 @@ describe('load', function() { done(); }); }); + + it('loads: open sans', function(done) { + fontnik.load(opensans, function(err, faces) { + assert.ifError(err); + assert.equal(faces[0].points.length, 882); + assert.equal(faces[0].family_name, 'Open Sans'); + assert.equal(faces[0].style_name, 'Regular'); + done(); + }); + }); + + it('invalid font loading', function(done) { + var baloneysans; + assert.throws(function() { + fontnik.load(baloneysans, function(err, faces) {}); + }); + done(); + }) +}); + +describe('range', function() { + it('ranges', function(done) { + fontnik.range(opensans, 0, 256, function(err, magic) { + assert.ifError(err); + assert.ok(magic); + var vt = new Glyphs(new Protobuf(new Uint8Array(magic))); + var json = JSON.parse(JSON.stringify(vt, nobuffer)); + jsonEqual('range', json); + fs.writeFile(__dirname + '/magic.pbf', magic, function(err) { + assert.ifError(err); + done(); + }); + }); + }); + + it('longrange', function(done) { + fontnik.range(opensans, 0, 1024, function(err, data) { + assert.ifError(err); + assert.ok(data); + done(); + }); + }); + + it('range typeerror filepath', function(done) { + assert.throws(function() { + fontnik.range(12, 0, 256, function(err, data) {}); + }, /First argument must be a path to a font/); + done(); + }); + + it('range typeerror start', function(done) { + assert.throws(function() { + fontnik.range(opensans, 'x', 256, function(err, data) {}); + }, /Second argument 'start' must be a number from 0-65535/); + assert.throws(function() { + fontnik.range(opensans, -3, 256, function(err, data) {}); + }, /Second argument 'start' must be a number from 0-65535/); + done(); + }); + + it('range typeerror end', function(done) { + assert.throws(function() { + fontnik.range(opensans, 0, 'y', function(err, data) {}); + }, /Third argument 'end' must be a number from 0-65535/); + assert.throws(function() { + fontnik.range(opensans, 0, 10000000, function(err, data) {}); + }, /Third argument 'end' must be a number from 0-65535/); + done(); + }); + + it('range typeerror lt', function(done) { + assert.throws(function() { + fontnik.range(opensans, 256, 0, function(err, data) {}); + }, /Start must be less than or equal to end/); + done(); + }); + + it('range typeerror lt', function(done) { + assert.throws(function() { + fontnik.range(opensans, 0, 256, ''); + }, /Callback must be a function/); + assert.throws(function() { + fontnik.range(opensans, 0, 256); + }, /Callback must be a function/); + done(); + }); }); diff --git a/vendor/fontnik/COPYING b/vendor/fontnik/COPYING deleted file mode 100644 index e5ab03e12..000000000 --- a/vendor/fontnik/COPYING +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/vendor/fontnik/include/fontnik/face.hpp b/vendor/fontnik/include/fontnik/face.hpp deleted file mode 100644 index 2ad7567a7..000000000 --- a/vendor/fontnik/include/fontnik/face.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/***************************************************************************** - * - * Copyright (C) 2014 Mapbox - * - * This file is part of Fontnik. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef FONTNIK_FACE_HPP -#define FONTNIK_FACE_HPP - -// mapnik -#include - -// boost -// undef B0 to workaround https://svn.boost.org/trac/boost/ticket/10467 -#undef B0 -#include -#include -#include -#include - -// stl -#include - -// freetype2 -extern "C" -{ -#include -#include FT_FREETYPE_H -#include FT_GLYPH_H -#include FT_OUTLINE_H -} - -namespace bg = boost::geometry; -namespace bgm = bg::model; -namespace bgi = bg::index; -typedef bgm::point Point; -typedef bgm::box Box; -typedef std::vector Points; -typedef std::vector Rings; -typedef std::pair SegmentPair; -typedef std::pair SegmentValue; -typedef bgi::rtree> Tree; - -namespace fontnik -{ - -class Face -{ - -public: - using glyph_info_cache_type = std::unordered_map; - - Face(FT_Face ft_face); - ~Face(); - - FT_Face get_face() const - { - return ft_face_; - } - - bool set_character_sizes(double size); - bool set_unscaled_character_sizes(); - - void RenderSDF(mapnik_fontnik::glyph_info &glyph, - int size, - int buffer, - float cutoff) const; - -private: - FT_Face ft_face_; - mutable glyph_info_cache_type glyph_info_cache_; - mutable double char_height_; -}; -typedef std::shared_ptr face_ptr; - -} // ns fontnik - -#endif // FONTNIK_FACE_HPP diff --git a/vendor/fontnik/include/fontnik/glyphs.hpp b/vendor/fontnik/include/fontnik/glyphs.hpp deleted file mode 100644 index e66775181..000000000 --- a/vendor/fontnik/include/fontnik/glyphs.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/***************************************************************************** - * - * Copyright (C) 2014 Mapbox - * - * This file is part of Fontnik. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef FONTNIK_GLYPHS_HPP -#define FONTNIK_GLYPHS_HPP - -#include -#include "glyphs.pb.h" - -namespace fontnik -{ - -class Glyphs -{ - -public: - Glyphs(); - Glyphs(const char *data, size_t length); - ~Glyphs(); - - std::string Serialize(); - void Range(std::string fontstack, - std::string range, - std::vector chars); - - static std::vector Codepoints(std::string fontstack); - - static std::string Trim(std::string str, std::string whitespace); - -public: - llmr::glyphs::glyphs glyphs; - -}; - -} // ns fontnik - -#endif // FONTNIK_GLYPHS_HPP diff --git a/vendor/fontnik/src/face.cpp b/vendor/fontnik/src/face.cpp deleted file mode 100644 index 2392a1218..000000000 --- a/vendor/fontnik/src/face.cpp +++ /dev/null @@ -1,377 +0,0 @@ -/***************************************************************************** - * - * Copyright (C) 2014 Mapbox - * - * This file is part of Fontnik. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -// fontnik -#include - -// agg -#include "agg_curves.h" - -#include - -namespace fontnik -{ - -struct User { - Rings rings; - Points ring; -}; - -Face::Face(FT_Face ft_face) - : ft_face_(ft_face), - char_height_(0.0) -{ -} - -Face::~Face() -{ - FT_Done_Face(ft_face_); -} - -bool Face::set_character_sizes(double size) -{ - char_height_ = 0.0; - return !FT_Set_Char_Size(ft_face_,0,(FT_F26Dot6)(size * (1<<6)),0,0); -} - -bool Face::set_unscaled_character_sizes() -{ - char_height_ = 0.0; - return !FT_Set_Char_Size(ft_face_,0,ft_face_->units_per_EM,0,0); -} - -void CloseRing(Points &ring) -{ - const Point &first = ring.front(); - const Point &last = ring.back(); - - if (first.get<0>() != last.get<0>() || - first.get<1>() != last.get<1>()) - { - ring.push_back(first); - } -} - -int MoveTo(const FT_Vector *to, void *ptr) -{ - User *user = (User*)ptr; - if (!user->ring.empty()) { - CloseRing(user->ring); - user->rings.push_back(user->ring); - user->ring.clear(); - } - user->ring.push_back(Point { float(to->x) / 64, float(to->y) / 64 }); - return 0; -} - -int LineTo(const FT_Vector *to, void *ptr) -{ - User *user = (User*)ptr; - user->ring.push_back(Point { float(to->x) / 64, float(to->y) / 64 }); - return 0; -} - -int ConicTo(const FT_Vector *control, - const FT_Vector *to, - void *ptr) -{ - User *user = (User*)ptr; - - Point prev = user->ring.back(); - - // pop off last point, duplicate of first point in bezier curve - user->ring.pop_back(); - - agg_fontnik::curve3_div curve(prev.get<0>(), prev.get<1>(), - float(control->x) / 64, float(control->y) / 64, - float(to->x) / 64, float(to->y) / 64); - - curve.rewind(0); - double x, y; - unsigned cmd; - - while (agg_fontnik::path_cmd_stop != (cmd = curve.vertex(&x, &y))) { - user->ring.push_back(Point {x, y}); - } - - return 0; -} - -int CubicTo(const FT_Vector *c1, - const FT_Vector *c2, - const FT_Vector *to, - void *ptr) -{ - User *user = (User*)ptr; - - Point prev = user->ring.back(); - - // pop off last point, duplicate of first point in bezier curve - user->ring.pop_back(); - - agg_fontnik::curve4_div curve(prev.get<0>(), prev.get<1>(), - float(c1->x) / 64, float(c1->y) / 64, - float(c2->x) / 64, float(c2->y) / 64, - float(to->x) / 64, float(to->y) / 64); - - curve.rewind(0); - double x, y; - unsigned cmd; - - while (agg_fontnik::path_cmd_stop != (cmd = curve.vertex(&x, &y))) { - user->ring.push_back(Point {x, y}); - } - - return 0; -} - -// point in polygon ray casting algorithm -bool PolyContainsPoint(const Rings &rings, const Point &p) -{ - bool c = false; - - for (const Points &ring : rings) { - auto p1 = ring.begin(); - auto p2 = p1 + 1; - - for (; p2 != ring.end(); p1++, p2++) { - if (((p1->get<1>() > p.get<1>()) != (p2->get<1>() > p.get<1>())) && (p.get<0>() < (p2->get<0>() - p1->get<0>()) * (p.get<1>() - p1->get<1>()) / (p2->get<1>() - p1->get<1>()) + p1->get<0>())) { - c = !c; - } - } - } - - return c; -} - -double SquaredDistance(const Point &v, const Point &w) -{ - const double a = v.get<0>() - w.get<0>(); - const double b = v.get<1>() - w.get<1>(); - return a * a + b * b; -} - -Point ProjectPointOnLineSegment(const Point &p, - const Point &v, - const Point &w) -{ - const double l2 = SquaredDistance(v, w); - if (l2 == 0) return v; - - const double t = ((p.get<0>() - v.get<0>()) * (w.get<0>() - v.get<0>()) + (p.get<1>() - v.get<1>()) * (w.get<1>() - v.get<1>())) / l2; - if (t < 0) return v; - if (t > 1) return w; - - return Point { - v.get<0>() + t * (w.get<0>() - v.get<0>()), - v.get<1>() + t * (w.get<1>() - v.get<1>()) - }; -} - -double SquaredDistanceToLineSegment(const Point &p, - const Point &v, - const Point &w) -{ - const Point s = ProjectPointOnLineSegment(p, v, w); - return SquaredDistance(p, s); -} - -double MinDistanceToLineSegment(const Tree &tree, - const Point &p, - int radius) -{ - const int squared_radius = radius * radius; - - std::vector results; - tree.query(bgi::intersects( - Box{ - Point{p.get<0>() - radius, p.get<1>() - radius}, - Point{p.get<0>() + radius, p.get<1>() + radius} - }), - std::back_inserter(results)); - - double sqaured_distance = std::numeric_limits::infinity(); - - for (const auto &value : results) { - const SegmentPair &segment = value.second; - const double dist = SquaredDistanceToLineSegment(p, - segment.first, - segment.second); - if (dist < sqaured_distance && dist < squared_radius) { - sqaured_distance = dist; - } - } - - return std::sqrt(sqaured_distance); -} - -void Face::RenderSDF(mapnik_fontnik::glyph_info &glyph, - int size, - int buffer, - float cutoff) const -{ - // Check if char is already in cache - glyph_info_cache_type::const_iterator itr; - itr = glyph_info_cache_.find(glyph.glyph_index); - if (itr != glyph_info_cache_.end()) { - glyph = itr->second; - return; - } - - if (FT_Load_Glyph (ft_face_, glyph.glyph_index, FT_LOAD_NO_HINTING)) { - return; - } - - FT_Glyph ft_glyph; - if (FT_Get_Glyph(ft_face_->glyph, &ft_glyph)) return; - - int advance = ft_face_->glyph->metrics.horiAdvance / 64; - int ascender = ft_face_->size->metrics.ascender / 64; - int descender = ft_face_->size->metrics.descender / 64; - - glyph.line_height = ft_face_->size->metrics.height; - glyph.advance = advance; - glyph.ascender = ascender; - glyph.descender = descender; - - FT_Outline_Funcs func_interface = { - .move_to = &MoveTo, - .line_to = &LineTo, - .conic_to = &ConicTo, - .cubic_to = &CubicTo, - .shift = 0, - .delta = 0 - }; - - User user; - - // Decompose outline into bezier curves and line segments - FT_Outline outline = ((FT_OutlineGlyph)ft_glyph)->outline; - if (FT_Outline_Decompose(&outline, &func_interface, &user)) return; - - if (!user.ring.empty()) { - CloseRing(user.ring); - user.rings.push_back(user.ring); - } - - if (user.rings.empty()) return; - - // Calculate the real glyph bbox. - double bbox_xmin = std::numeric_limits::infinity(), - bbox_ymin = std::numeric_limits::infinity(); - - double bbox_xmax = -std::numeric_limits::infinity(), - bbox_ymax = -std::numeric_limits::infinity(); - - for (const Points &ring : user.rings) { - for (const Point &point : ring) { - if (point.get<0>() > bbox_xmax) bbox_xmax = point.get<0>(); - if (point.get<0>() < bbox_xmin) bbox_xmin = point.get<0>(); - if (point.get<1>() > bbox_ymax) bbox_ymax = point.get<1>(); - if (point.get<1>() < bbox_ymin) bbox_ymin = point.get<1>(); - } - } - - bbox_xmin = std::round(bbox_xmin); - bbox_ymin = std::round(bbox_ymin); - bbox_xmax = std::round(bbox_xmax); - bbox_ymax = std::round(bbox_ymax); - - // Offset so that glyph outlines are in the bounding box. - for (Points &ring : user.rings) { - for (Point &point : ring) { - point.set<0>(point.get<0>() + -bbox_xmin + buffer); - point.set<1>(point.get<1>() + -bbox_ymin + buffer); - } - } - - if (bbox_xmax - bbox_xmin == 0 || bbox_ymax - bbox_ymin == 0) return; - - glyph.left = bbox_xmin; - glyph.top = bbox_ymax; - glyph.width = bbox_xmax - bbox_xmin; - glyph.height = bbox_ymax - bbox_ymin; - - Tree tree; - float offset = 0.5; - int radius = 8; - - for (const Points &ring : user.rings) { - auto p1 = ring.begin(); - auto p2 = p1 + 1; - - for (; p2 != ring.end(); p1++, p2++) { - const int segment_x1 = std::min(p1->get<0>(), p2->get<0>()); - const int segment_x2 = std::max(p1->get<0>(), p2->get<0>()); - const int segment_y1 = std::min(p1->get<1>(), p2->get<1>()); - const int segment_y2 = std::max(p1->get<1>(), p2->get<1>()); - - tree.insert(SegmentValue { - Box { - Point {segment_x1, segment_y1}, - Point {segment_x2, segment_y2} - }, - SegmentPair { - Point {p1->get<0>(), p1->get<1>()}, - Point {p2->get<0>(), p2->get<1>()} - } - }); - } - } - - // Loop over every pixel and determine the positive/negative distance to the outline. - unsigned int buffered_width = glyph.width + 2 * buffer; - unsigned int buffered_height = glyph.height + 2 * buffer; - unsigned int bitmap_size = buffered_width * buffered_height; - glyph.bitmap.resize(bitmap_size); - - for (unsigned int y = 0; y < buffered_height; y++) { - for (unsigned int x = 0; x < buffered_width; x++) { - unsigned int ypos = buffered_height - y - 1; - unsigned int i = ypos * buffered_width + x; - - double d = MinDistanceToLineSegment(tree, Point {x + offset, y + offset}, radius) * (256 / radius); - - // Invert if point is inside. - const bool inside = PolyContainsPoint(user.rings, Point { x + offset, y + offset }); - if (inside) { - d = -d; - } - - // Shift the 0 so that we can fit a few negative values - // into our 8 bits. - d += cutoff * 256; - - // Clamp to 0-255 to prevent overflows or underflows. - int n = d > 255 ? 255 : d; - n = n < 0 ? 0 : n; - - glyph.bitmap[i] = static_cast(255 - n); - } - } - - FT_Done_Glyph(ft_glyph); - - glyph_info_cache_.emplace(glyph.glyph_index, glyph); -} - -} // ns fontnik diff --git a/vendor/fontnik/src/glyphs.cpp b/vendor/fontnik/src/glyphs.cpp deleted file mode 100644 index 4ff84c2b1..000000000 --- a/vendor/fontnik/src/glyphs.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/***************************************************************************** - * - * Copyright (C) 2014 Mapbox - * - * This file is part of Fontnik. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -// fontnik -#include -#include -#include - -// stl -#include -#include -#include - -// freetype2 -extern "C" -{ -#include -#include FT_FREETYPE_H -} - -namespace fontnik -{ - -Glyphs::Glyphs() {} - -Glyphs::Glyphs(const char *data, size_t length) -{ - glyphs.ParseFromArray(data, length); -} - -Glyphs::~Glyphs() {} - -std::string Glyphs::Serialize() -{ - return glyphs.SerializeAsString(); -} - -void Glyphs::Range(std::string fontstack, - std::string range, - std::vector chars) -{ - mapnik_fontnik::freetype_engine font_engine_; - mapnik_fontnik::face_manager_freetype font_manager(font_engine_); - - mapnik_fontnik::font_set font_set(fontstack); - std::stringstream stream(fontstack); - std::string face_name; - - // TODO: better to split on delim and font_names_.reserve() then add? - while (std::getline(stream, face_name, ',')) { - font_set.add_face_name(Trim(face_name, " \t")); - } - - mapnik_fontnik::face_set_ptr face_set; - - // This may throw. - face_set = font_manager.get_face_set(font_set); - - llmr::glyphs::fontstack *mutable_fontstack = glyphs.add_stacks(); - mutable_fontstack->set_name(fontstack); - mutable_fontstack->set_range(range); - - const double scale_factor = 1.0; - - // Set character sizes. - double size = 24 * scale_factor; - face_set->set_character_sizes(size); - - for (std::vector::size_type i = 0; i != chars.size(); i++) { - FT_ULong char_code = chars[i]; - mapnik_fontnik::glyph_info glyph; - - for (auto const& face : *face_set) { - // Get FreeType face from face_ptr. - FT_Face ft_face = face->get_face(); - FT_UInt char_index = FT_Get_Char_Index(ft_face, char_code); - - // Try next font in fontset. - if (!char_index) continue; - - glyph.glyph_index = char_index; - face->RenderSDF(glyph, 24, 3, 0.25); - - // Add glyph to fontstack. - llmr::glyphs::glyph *mutable_glyph = mutable_fontstack->add_glyphs(); - mutable_glyph->set_id(char_code); - mutable_glyph->set_width(glyph.width); - mutable_glyph->set_height(glyph.height); - mutable_glyph->set_left(glyph.left); - mutable_glyph->set_top(glyph.top - glyph.ascender); - mutable_glyph->set_advance(glyph.advance); - - if (glyph.width > 0) { - mutable_glyph->set_bitmap(glyph.bitmap); - } - - // Glyph added, continue to next char_code. - break; - } - } -} - -std::vector Glyphs::Codepoints(std::string fontstack) -{ - std::vector points; - mapnik_fontnik::freetype_engine font_engine_; - mapnik_fontnik::face_manager_freetype font_manager(font_engine_); - - mapnik_fontnik::font_set font_set(fontstack); - std::stringstream stream(fontstack); - std::string face_name; - - while (std::getline(stream, face_name, ',')) { - font_set.add_face_name(Trim(face_name, " \t")); - } - - mapnik_fontnik::face_set_ptr face_set; - - // This may throw. - face_set = font_manager.get_face_set(font_set); - for (auto const& face : *face_set) { - FT_Face ft_face = face->get_face(); - 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); - } - } - - // for font sets, eliminate duplicates of codepoints - // that are shared. - std::sort(points.begin(), points.end()); - auto last = std::unique(points.begin(), points.end()); - points.erase(last, points.end()); - - return points; -} - -std::string Glyphs::Trim(std::string str, std::string whitespace) -{ - const auto strBegin = str.find_first_not_of(whitespace); - - if (strBegin == std::string::npos) { - return ""; - } - - const auto strEnd = str.find_last_not_of(whitespace); - const auto strRange = strEnd - strBegin + 1; - - return str.substr(strBegin, strRange); -} - -} // ns fontnik diff --git a/vendor/mapnik/COPYING b/vendor/mapnik/COPYING deleted file mode 100644 index e5ab03e12..000000000 --- a/vendor/mapnik/COPYING +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/vendor/mapnik/README.fontserver b/vendor/mapnik/README.fontserver deleted file mode 100644 index 59680e7fe..000000000 --- a/vendor/mapnik/README.fontserver +++ /dev/null @@ -1,25 +0,0 @@ -Name: mapnik -URL: https://github.com/mapnik/mapnik -Version: 3.x -License: LGPL 2.1 -Description: Mapnik is an open source toolkit for developing mapping applications -Local Modifications: -- font_engine_freetype.hpp - - Assume MAPNIK_THREADSAFE - - Remove FT_Stroker -- text/face_set.hpp - - Split out font_face_set class from face.hpp - - Replace mapnik::face_ptr with fontnik::face_ptr -- text/glyph_info.hpp - - Store signed distance field in std::string bitmap - - Add left, top, width, height, advance, ascender and descender metrics - - Set metrics directly rather than calculating from unscaled values - - Add operator< overload for sorting -- font_engine_freetype.cpp - - More verbose error logging - - Assume MAPNIK_THREADSAFE and MAPNIK_LOG - - Replace mapnik::face_ptr with fontnik::face_ptr - - Remove FT_Stroker -- text/face_set.cpp - - Split out font_face_set class from face.cpp - - Replace mapnik::face_ptr with fontnik::face_ptr diff --git a/vendor/mapnik/include/mapnik/config.hpp b/vendor/mapnik/include/mapnik/config.hpp deleted file mode 100644 index 6e4fe6858..000000000 --- a/vendor/mapnik/include/mapnik/config.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef MAPNIK_CONFIG_HPP -#define MAPNIK_CONFIG_HPP - -// Windows DLL support - -#ifdef _WINDOWS -# define MAPNIK_EXP __declspec (dllexport) -# define MAPNIK_IMP __declspec (dllimport) -# ifdef MAPNIK_EXPORTS -# define MAPNIK_DECL __declspec (dllexport) -# else -# define MAPNIK_DECL __declspec (dllimport) -# endif -# pragma warning( disable: 4251 ) -# pragma warning( disable: 4275 ) -# if (_MSC_VER >= 1400) // vc8 -# pragma warning(disable : 4996) //_CRT_SECURE_NO_DEPRECATE -# endif -#else -# if __GNUC__ >= 4 -# define MAPNIK_EXP __attribute__ ((visibility ("default"))) -# define MAPNIK_DECL __attribute__ ((visibility ("default"))) -# define MAPNIK_IMP __attribute__ ((visibility ("default"))) -# else -# define MAPNIK_EXP -# define MAPNIK_DECL -# define MAPNIK_IMP -# endif -#endif - -#define PROJ_ENVELOPE_POINTS 20 - -#endif // MAPNIK_CONFIG_HPP diff --git a/vendor/mapnik/include/mapnik/debug.hpp b/vendor/mapnik/include/mapnik/debug.hpp deleted file mode 100644 index 41223366e..000000000 --- a/vendor/mapnik/include/mapnik/debug.hpp +++ /dev/null @@ -1,330 +0,0 @@ -/***************************************************************************** - * - * This file is part of Mapnik (c++ mapping toolkit) - * - * Copyright (C) 2011 Artem Pavlenko - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - *****************************************************************************/ - -#ifndef MAPNIK_DEBUG_HPP -#define MAPNIK_DEBUG_HPP - -// mapnik (should not depend on anything that need to use this) -#include -#include -#include -#include - -// std -#include -#include -#include -#include -#include -#include -#ifdef MAPNIK_THREADSAFE -#include -#endif - -namespace mapnik_fontnik { - - /* - Global logger class that holds the configuration of severity, format - and file/console redirection. - */ - class MAPNIK_DECL logger : - public singleton, - private mapnik_fontnik::noncopyable - { - public: - enum severity_type - { - debug = 0, - warn = 1, - error = 2, - none = 3 - }; - - using severity_map = std::unordered_map; - - // global security level - static severity_type get_severity() - { - return severity_level_; - } - - static void set_severity(severity_type const& severity_level) - { -#ifdef MAPNIK_THREADSAFE - mapnik_fontnik::scoped_lock lock(severity_mutex_); -#endif - - severity_level_ = severity_level; - } - - // per object security levels - static severity_type get_object_severity(std::string const& object_name) - { - severity_map::iterator it = object_severity_level_.find(object_name); - if (object_name.empty() || it == object_severity_level_.end()) - { - return severity_level_; - } - else - { - return it->second; - } - } - - static void set_object_severity(std::string const& object_name, - severity_type const& security_level) - { -#ifdef MAPNIK_THREADSAFE - mapnik_fontnik::scoped_lock lock(severity_mutex_); -#endif - if (! object_name.empty()) - { - object_severity_level_[object_name] = security_level; - } - } - - static void clear_object_severity() - { -#ifdef MAPNIK_THREADSAFE - mapnik_fontnik::scoped_lock lock(severity_mutex_); -#endif - - object_severity_level_.clear(); - } - - // format - static std::string get_format() - { - return format_; - } - - static void set_format(std::string const& format) - { -#ifdef MAPNIK_THREADSAFE - mapnik_fontnik::scoped_lock lock(format_mutex_); -#endif - format_ = format; - } - - // interpolate the format string for output - static std::string str(); - - // output - static void use_file(std::string const& filepath); - static void use_console(); - - private: - static severity_type severity_level_; - static severity_map object_severity_level_; - static bool severity_env_check_; - - static std::string format_; - static bool format_env_check_; - - static std::ofstream file_output_; - static std::string file_name_; - static std::streambuf* saved_buf_; - -#ifdef MAPNIK_THREADSAFE - static std::mutex severity_mutex_; - static std::mutex format_mutex_; -#endif - }; - - - namespace detail { - - /* - Default sink, it regulates access to clog - */ - template - class clog_sink - { - public: - using stream_buffer = std::basic_ostringstream; - - void operator()(logger::severity_type const& /*severity*/, stream_buffer const& s) - { -#ifdef MAPNIK_THREADSAFE - static std::mutex mutex; - mapnik_fontnik::scoped_lock lock(mutex); -#endif - std::clog << logger::str() << " " << s.str() << std::endl; - } - }; - - - /* - Base log class, should not log anything when no MAPNIK_LOG is defined - - This is used for debug/warn reporting that should not output - anything when not compiling for speed. - */ - template