Skip to content

Commit f62a0af

Browse files
committed
Add support for extracting metadata about the font like weight, width, obliqueness, and italic flag
1 parent ddffc4f commit f62a0af

File tree

4 files changed

+114
-18
lines changed

4 files changed

+114
-18
lines changed
134 KB
Binary file not shown.

src/glyphs.cpp

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
#include <mapbox/glyph_foundry_impl.hpp>
1515
#include <utility>
1616

17+
// freetype2
18+
extern "C" {
19+
#include <ft2build.h>
20+
#include FT_TRUETYPE_TABLES_H
21+
}
22+
1723
namespace node_fontnik {
1824

1925
struct FaceMetadata {
@@ -26,17 +32,12 @@ struct FaceMetadata {
2632

2733
std::string family_name{};
2834
std::string style_name{};
35+
uint16_t weight{};
36+
float width{};
37+
bool italic{};
38+
float oblique{};
39+
2940
std::vector<int> points{};
30-
FaceMetadata(std::string _family_name,
31-
std::string _style_name,
32-
std::vector<int>&& _points)
33-
: family_name(std::move(_family_name)),
34-
style_name(std::move(_style_name)),
35-
points(std::move(_points)) {}
36-
FaceMetadata(std::string _family_name,
37-
std::vector<int>&& _points)
38-
: family_name(std::move(_family_name)),
39-
points(std::move(_points)) {}
4041
};
4142

4243
struct GlyphPBF {
@@ -88,6 +89,31 @@ struct ft_face_guard {
8889
FT_Face* face_;
8990
};
9091

92+
93+
float getWidth(FT_UInt16 usWidthClass) {
94+
switch (usWidthClass) {
95+
case /* FWIDTH_ULTRA_CONDENSED */ 1:
96+
return 50.f;
97+
case /* FWIDTH_EXTRA_CONDENSED */ 2:
98+
return 62.5f;
99+
case /* FWIDTH_CONDENSED */ 3:
100+
return 75.f;
101+
case /* FWIDTH_SEMI_CONDENSED */ 4:
102+
return 87.5f;
103+
default:
104+
case /* FWIDTH_NORMAL */ 5:
105+
return 100.f;
106+
case /* FWIDTH_SEMI_EXPANDED */ 6:
107+
return 112.5f;
108+
case /* FWIDTH_EXPANDED */ 7:
109+
return 125.f;
110+
case /* FWIDTH_EXTRA_EXPANDED */ 8:
111+
return 150.f;
112+
case /* FWIDTH_ULTRA_EXPANDED */ 9:
113+
return 200.f;
114+
}
115+
}
116+
91117
struct AsyncLoad : Napi::AsyncWorker {
92118
using Base = Napi::AsyncWorker;
93119
AsyncLoad(Napi::Buffer<char> const& buffer, Napi::Function const& callback)
@@ -123,6 +149,11 @@ struct AsyncLoad : Napi::AsyncWorker {
123149
faces_.reserve(static_cast<std::size_t>(num_faces));
124150
}
125151
if (ft_face->family_name != nullptr) {
152+
FaceMetadata metadata{ft_face->family_name};
153+
if (ft_face->style_name) {
154+
metadata.style_name = ft_face->style_name;
155+
}
156+
126157
std::set<int> points;
127158
FT_ULong charcode;
128159
FT_UInt gindex;
@@ -131,12 +162,43 @@ struct AsyncLoad : Napi::AsyncWorker {
131162
charcode = FT_Get_Next_Char(ft_face, charcode, &gindex);
132163
if (charcode != 0) points.emplace(charcode);
133164
}
134-
std::vector<int> points_vec(points.begin(), points.end());
135-
if (ft_face->style_name != nullptr) {
136-
faces_.emplace_back(ft_face->family_name, ft_face->style_name, std::move(points_vec));
137-
} else {
138-
faces_.emplace_back(ft_face->family_name, std::move(points_vec));
165+
metadata.points = std::vector<int>(points.begin(), points.end());
166+
167+
TT_Header* head = (TT_Header*)FT_Get_Sfnt_Table(ft_face, FT_SFNT_HEAD);
168+
TT_OS2* os2 = (TT_OS2*)FT_Get_Sfnt_Table(ft_face, FT_SFNT_OS2);
169+
TT_Postscript* post = (TT_Postscript*)FT_Get_Sfnt_Table(ft_face, FT_SFNT_POST);
170+
171+
// Weight
172+
if (os2) {
173+
metadata.weight = os2->usWeightClass;
174+
} else if (head) {
175+
metadata.weight = (head->Mac_Style & (/* condensed */ 1u << 0)) ? 700 : 400;
176+
}
177+
178+
// Width
179+
if (os2) {
180+
metadata.width = getWidth(os2->usWidthClass);
181+
} else if (head) {
182+
if (head->Mac_Style & (/* condensed */ 1u << 5)) {
183+
metadata.width = 75.f;
184+
} else if (head->Mac_Style & (/* expanded */ 1u << 6)) {
185+
metadata.width = 125.f;
186+
}
139187
}
188+
189+
// Italic
190+
if (os2) {
191+
metadata.italic = os2->fsSelection & (/* italic */ 1u << 0);
192+
} else if (head) {
193+
metadata.italic = head->Mac_Style & (/* italic */ 1u << 1);
194+
}
195+
196+
// Slant
197+
if (post) {
198+
metadata.oblique = post->italicAngle / float(1 << 16);
199+
}
200+
201+
faces_.emplace_back(std::move(metadata));
140202
} else {
141203
SetError("font does not have family_name or style_name");
142204
return;
@@ -156,6 +218,10 @@ struct AsyncLoad : Napi::AsyncWorker {
156218
if (!face.style_name.empty()) {
157219
js_face.Set("style_name", face.style_name);
158220
}
221+
js_face.Set("weight", face.weight);
222+
js_face.Set("width", face.width);
223+
js_face.Set("italic", face.italic);
224+
js_face.Set("oblique", face.oblique);
159225
Napi::Array js_points = Napi::Array::New(env, face.points.size());
160226
std::uint32_t p_idx = 0;
161227
for (auto const& pt : face.points) {

src/glyphs.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22

33
#include <napi.h>
4-
#include <uv.h>
54

65
namespace node_fontnik {
76

test/fontnik.test.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,50 @@ function jsonEqual(t, key, json) {
2424
var expected = JSON.parse(fs.readFileSync(__dirname + '/expected/load.json').toString());
2525
var firasans = fs.readFileSync(path.resolve(__dirname + '/../fonts/firasans-medium/FiraSans-Medium.ttf'));
2626
var opensans = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-Regular.ttf'));
27+
var opensans_lightitalic = fs.readFileSync(path.resolve(__dirname + '/../fonts/open-sans/OpenSans-LightItalic.ttf'));
2728
var invalid_no_family = fs.readFileSync(path.resolve(__dirname + '/fixtures/fonts-invalid/1c2c3fc37b2d4c3cb2ef726c6cdaaabd4b7f3eb9.ttf'));
2829
var guardianbold = fs.readFileSync(path.resolve(__dirname + '/../fonts/GuardianTextSansWeb/GuardianTextSansWeb-Bold.ttf'));
2930
var osaka = fs.readFileSync(path.resolve(__dirname + '/../fonts/osaka/Osaka.ttf'));
3031

3132
test('load', function(t) {
32-
t.test('loads: Fira Sans', function(t) {
33+
t.test('loads: Fira Sans Medium', function(t) {
3334
fontnik.load(firasans, function(err, faces) {
3435
t.error(err);
3536
t.equal(faces[0].points.length, 789);
3637
t.equal(faces[0].family_name, 'Fira Sans');
3738
t.equal(faces[0].style_name, 'Medium');
39+
t.equal(faces[0].weight, 500);
40+
t.equal(faces[0].width, 100);
41+
t.equal(faces[0].italic, false);
42+
t.equal(faces[0].oblique, 0);
3843
t.end();
3944
});
4045
});
4146

42-
t.test('loads: Open Sans', function(t) {
47+
t.test('loads: Open Sans Regular', function(t) {
4348
fontnik.load(opensans, function(err, faces) {
4449
t.error(err);
4550
t.equal(faces[0].points.length, 882);
4651
t.equal(faces[0].family_name, 'Open Sans');
4752
t.equal(faces[0].style_name, 'Regular');
53+
t.equal(faces[0].weight, 400);
54+
t.equal(faces[0].width, 100);
55+
t.equal(faces[0].italic, false);
56+
t.equal(faces[0].oblique, 0);
57+
t.end();
58+
});
59+
});
60+
61+
t.test('loads: Open Sans Light Italic', function(t) {
62+
fontnik.load(opensans_lightitalic, function(err, faces) {
63+
t.error(err);
64+
t.equal(faces[0].points.length, 1009);
65+
t.equal(faces[0].family_name, 'Open Sans');
66+
t.equal(faces[0].style_name, 'Light Italic');
67+
t.equal(faces[0].weight, 300);
68+
t.equal(faces[0].width, 100);
69+
t.equal(faces[0].italic, true);
70+
t.equal(faces[0].oblique, -12);
4871
t.end();
4972
});
5073
});
@@ -57,6 +80,10 @@ test('load', function(t) {
5780
t.equal(faces[0].family_name, '?');
5881
t.equal(faces[0].hasOwnProperty('style_name'), false);
5982
t.equal(faces[0].style_name, undefined);
83+
t.equal(faces[0].weight, 700);
84+
t.equal(faces[0].width, 100);
85+
t.equal(faces[0].italic, false);
86+
t.equal(faces[0].oblique, 0);
6087
t.end();
6188
});
6289
});
@@ -66,6 +93,10 @@ test('load', function(t) {
6693
t.error(err);
6794
t.equal(faces[0].family_name, 'Osaka');
6895
t.equal(faces[0].style_name, 'Regular');
96+
t.equal(faces[0].weight, 400);
97+
t.equal(faces[0].width, 100);
98+
t.equal(faces[0].italic, false);
99+
t.equal(faces[0].oblique, 0);
69100
t.end();
70101
});
71102
});

0 commit comments

Comments
 (0)