diff --git a/fonts/open-sans/OpenSans-LightItalic.ttf b/fonts/open-sans/OpenSans-LightItalic.ttf new file mode 100644 index 000000000..d913f356a Binary files /dev/null and b/fonts/open-sans/OpenSans-LightItalic.ttf differ diff --git a/src/glyphs.cpp b/src/glyphs.cpp index 2437e3ae8..bae4e0762 100644 --- a/src/glyphs.cpp +++ b/src/glyphs.cpp @@ -14,6 +14,12 @@ #include #include +// freetype2 +extern "C" { +#include +#include FT_TRUETYPE_TABLES_H +} + namespace node_fontnik { struct FaceMetadata { @@ -26,17 +32,12 @@ struct FaceMetadata { std::string family_name{}; std::string style_name{}; + uint16_t weight{}; + float width{}; + bool italic{}; + float oblique{}; + std::vector points{}; - FaceMetadata(std::string _family_name, - std::string _style_name, - std::vector&& _points) - : family_name(std::move(_family_name)), - style_name(std::move(_style_name)), - points(std::move(_points)) {} - FaceMetadata(std::string _family_name, - std::vector&& _points) - : family_name(std::move(_family_name)), - points(std::move(_points)) {} }; struct GlyphPBF { @@ -88,6 +89,31 @@ struct ft_face_guard { FT_Face* face_; }; + +float getWidth(FT_UInt16 usWidthClass) { + switch (usWidthClass) { + case /* FWIDTH_ULTRA_CONDENSED */ 1: + return 50.f; + case /* FWIDTH_EXTRA_CONDENSED */ 2: + return 62.5f; + case /* FWIDTH_CONDENSED */ 3: + return 75.f; + case /* FWIDTH_SEMI_CONDENSED */ 4: + return 87.5f; + default: + case /* FWIDTH_NORMAL */ 5: + return 100.f; + case /* FWIDTH_SEMI_EXPANDED */ 6: + return 112.5f; + case /* FWIDTH_EXPANDED */ 7: + return 125.f; + case /* FWIDTH_EXTRA_EXPANDED */ 8: + return 150.f; + case /* FWIDTH_ULTRA_EXPANDED */ 9: + return 200.f; + } +} + struct AsyncLoad : Napi::AsyncWorker { using Base = Napi::AsyncWorker; AsyncLoad(Napi::Buffer const& buffer, Napi::Function const& callback) @@ -123,6 +149,11 @@ struct AsyncLoad : Napi::AsyncWorker { faces_.reserve(static_cast(num_faces)); } if (ft_face->family_name != nullptr) { + FaceMetadata metadata{ft_face->family_name}; + if (ft_face->style_name) { + metadata.style_name = ft_face->style_name; + } + std::set points; FT_ULong charcode; FT_UInt gindex; @@ -131,12 +162,43 @@ struct AsyncLoad : Napi::AsyncWorker { charcode = FT_Get_Next_Char(ft_face, charcode, &gindex); if (charcode != 0) points.emplace(charcode); } - std::vector points_vec(points.begin(), points.end()); - if (ft_face->style_name != nullptr) { - faces_.emplace_back(ft_face->family_name, ft_face->style_name, std::move(points_vec)); - } else { - faces_.emplace_back(ft_face->family_name, std::move(points_vec)); + metadata.points = std::vector(points.begin(), points.end()); + + TT_Header* head = reinterpret_cast(FT_Get_Sfnt_Table(ft_face, FT_SFNT_HEAD)); + TT_OS2* os2 = reinterpret_cast(FT_Get_Sfnt_Table(ft_face, FT_SFNT_OS2)); + TT_Postscript* post = reinterpret_cast(FT_Get_Sfnt_Table(ft_face, FT_SFNT_POST)); + + // Weight + if (os2) { + metadata.weight = os2->usWeightClass; + } else if (head) { + metadata.weight = (head->Mac_Style & (/* condensed */ 1u << 0)) ? 700 : 400; + } + + // Width + if (os2) { + metadata.width = getWidth(os2->usWidthClass); + } else if (head) { + if (head->Mac_Style & (/* condensed */ 1u << 5)) { + metadata.width = 75.f; + } else if (head->Mac_Style & (/* expanded */ 1u << 6)) { + metadata.width = 125.f; + } } + + // Italic + if (os2) { + metadata.italic = os2->fsSelection & (/* italic */ 1u << 0); + } else if (head) { + metadata.italic = head->Mac_Style & (/* italic */ 1u << 1); + } + + // Slant + if (post) { + metadata.oblique = static_cast(post->italicAngle) / float(1 << 16); + } + + faces_.emplace_back(std::move(metadata)); } else { SetError("font does not have family_name or style_name"); return; @@ -156,6 +218,10 @@ struct AsyncLoad : Napi::AsyncWorker { if (!face.style_name.empty()) { js_face.Set("style_name", face.style_name); } + js_face.Set("weight", face.weight); + js_face.Set("width", face.width); + js_face.Set("italic", face.italic); + js_face.Set("oblique", face.oblique); Napi::Array js_points = Napi::Array::New(env, face.points.size()); std::uint32_t p_idx = 0; for (auto const& pt : face.points) { diff --git a/src/glyphs.hpp b/src/glyphs.hpp index 33078736a..798c1bc95 100644 --- a/src/glyphs.hpp +++ b/src/glyphs.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace node_fontnik { diff --git a/test/fontnik.test.js b/test/fontnik.test.js index a1505c3ce..07725f6b0 100644 --- a/test/fontnik.test.js +++ b/test/fontnik.test.js @@ -24,27 +24,50 @@ function jsonEqual(t, key, json) { var expected = JSON.parse(fs.readFileSync(__dirname + '/expected/load.json').toString()); var firasans = fs.readFileSync(path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf')); var opensans = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf')); +var opensans_lightitalic = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-LightItalic.ttf')); var invalid_no_family = fs.readFileSync(path.resolve(__dirname + '/fixtures/fonts-invalid/1c2c3fc37b2d4c3cb2ef726c6cdaaabd4b7f3eb9.ttf')); var guardianbold = fs.readFileSync(path.resolve(__dirname + '/../fonts/GuardianTextSansWeb/GuardianTextSansWeb-Bold.ttf')); var osaka = fs.readFileSync(path.resolve(__dirname + '/../fonts/osaka/Osaka.ttf')); test('load', function(t) { - t.test('loads: Fira Sans', function(t) { + t.test('loads: Fira Sans Medium', function(t) { fontnik.load(firasans, function(err, faces) { t.error(err); t.equal(faces[0].points.length, 789); t.equal(faces[0].family_name, 'Fira Sans'); t.equal(faces[0].style_name, 'Medium'); + t.equal(faces[0].weight, 500); + t.equal(faces[0].width, 100); + t.equal(faces[0].italic, false); + t.equal(faces[0].oblique, 0); t.end(); }); }); - t.test('loads: Open Sans', function(t) { + t.test('loads: Open Sans Regular', function(t) { fontnik.load(opensans, function(err, faces) { t.error(err); t.equal(faces[0].points.length, 882); t.equal(faces[0].family_name, 'Open Sans'); t.equal(faces[0].style_name, 'Regular'); + t.equal(faces[0].weight, 400); + t.equal(faces[0].width, 100); + t.equal(faces[0].italic, false); + t.equal(faces[0].oblique, 0); + t.end(); + }); + }); + + t.test('loads: Open Sans Light Italic', function(t) { + fontnik.load(opensans_lightitalic, function(err, faces) { + t.error(err); + t.equal(faces[0].points.length, 1009); + t.equal(faces[0].family_name, 'Open Sans'); + t.equal(faces[0].style_name, 'Light Italic'); + t.equal(faces[0].weight, 300); + t.equal(faces[0].width, 100); + t.equal(faces[0].italic, true); + t.equal(faces[0].oblique, -12); t.end(); }); }); @@ -57,6 +80,10 @@ test('load', function(t) { t.equal(faces[0].family_name, '?'); t.equal(faces[0].hasOwnProperty('style_name'), false); t.equal(faces[0].style_name, undefined); + t.equal(faces[0].weight, 700); + t.equal(faces[0].width, 100); + t.equal(faces[0].italic, false); + t.equal(faces[0].oblique, 0); t.end(); }); }); @@ -66,6 +93,10 @@ test('load', function(t) { t.error(err); t.equal(faces[0].family_name, 'Osaka'); t.equal(faces[0].style_name, 'Regular'); + t.equal(faces[0].weight, 400); + t.equal(faces[0].width, 100); + t.equal(faces[0].italic, false); + t.equal(faces[0].oblique, 0); t.end(); }); });