-
Notifications
You must be signed in to change notification settings - Fork 920
/
FontUtils.js
185 lines (155 loc) · 4.6 KB
/
FontUtils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
'use strict';
var FontFace = require('./FontFace');
var _useNativeImpl = (typeof window.FontFace !== 'undefined');
var _pendingFonts = {};
var _loadedFonts = {};
var _failedFonts = {};
var kFontLoadTimeout = 3000;
/**
* Check if a font face has loaded
* @param {FontFace} fontFace
* @return {Boolean}
*/
function isFontLoaded (fontFace) {
// For remote URLs, check the cache. System fonts (sans url) assume loaded.
return _loadedFonts[fontFace.id] !== undefined || !fontFace.url;
}
/**
* Load a remote font and execute a callback.
* @param {FontFace} fontFace The font to Load
* @param {Function} callback Function executed upon font Load
*/
function loadFont (fontFace, callback) {
var defaultNode;
var testNode;
var checkFont;
// See if we've previously loaded it.
if (_loadedFonts[fontFace.id]) {
return callback(null);
}
// See if we've previously failed to load it.
if (_failedFonts[fontFace.id]) {
return callback(_failedFonts[fontFace.id]);
}
// System font: assume already loaded.
if (!fontFace.url) {
return callback(null);
}
// Font load is already in progress:
if (_pendingFonts[fontFace.id]) {
_pendingFonts[fontFace.id].callbacks.push(callback);
return;
}
// Create the test <span>'s for measuring.
defaultNode = createTestNode('Helvetica', fontFace.attributes);
testNode = createTestNode(fontFace.family, fontFace.attributes);
document.body.appendChild(testNode);
document.body.appendChild(defaultNode);
_pendingFonts[fontFace.id] = {
startTime: Date.now(),
defaultNode: defaultNode,
testNode: testNode,
callbacks: [callback]
};
// Font watcher
checkFont = function () {
var currWidth = testNode.getBoundingClientRect().width;
var defaultWidth = defaultNode.getBoundingClientRect().width;
var loaded = currWidth !== defaultWidth;
if (loaded) {
handleFontLoad(fontFace, null);
} else {
// Timeout?
if (Date.now() - _pendingFonts[fontFace.id].startTime >= kFontLoadTimeout) {
handleFontLoad(fontFace, true);
} else {
requestAnimationFrame(checkFont);
}
}
};
// Start watching
checkFont();
}
// Internal
// ========
/**
* Native FontFace loader implementation
* @internal
*/
function loadFontNative (fontFace, callback) {
var theFontFace;
// See if we've previously loaded it.
if (_loadedFonts[fontFace.id]) {
return callback(null);
}
// See if we've previously failed to load it.
if (_failedFonts[fontFace.id]) {
return callback(_failedFonts[fontFace.id]);
}
// System font: assume it's installed.
if (!fontFace.url) {
return callback(null);
}
// Font load is already in progress:
if (_pendingFonts[fontFace.id]) {
_pendingFonts[fontFace.id].callbacks.push(callback);
return;
}
_pendingFonts[fontFace.id] = {
startTime: Date.now(),
callbacks: [callback]
};
// Use font loader API
theFontFace = new window.FontFace(fontFace.family,
'url(' + fontFace.url + ')', fontFace.attributes);
theFontFace.load().then(function () {
_loadedFonts[fontFace.id] = true;
callback(null);
}, function (err) {
_failedFonts[fontFace.id] = err;
callback(err);
});
}
/**
* Helper method for created a hidden <span> with a given font.
* Uses TypeKit's default test string, which is said to result
* in highly varied measured widths when compared to the default font.
* @internal
*/
function createTestNode (family, attributes) {
var span = document.createElement('span');
span.setAttribute('data-fontfamily', family);
span.style.cssText = 'position:absolute; left:-5000px; top:-5000px; visibility:hidden;' +
'font-size:100px; font-family:"' + family + '", Helvetica;font-weight: ' + attributes.weight + ';' +
'font-style:' + attributes.style + ';';
span.innerHTML = 'BESs';
return span;
}
/**
* @internal
*/
function handleFontLoad (fontFace, timeout) {
var error = timeout ? 'Exceeded load timeout of ' + kFontLoadTimeout + 'ms' : null;
if (!error) {
_loadedFonts[fontFace.id] = true;
} else {
_failedFonts[fontFace.id] = error;
}
// Execute pending callbacks.
_pendingFonts[fontFace.id].callbacks.forEach(function (callback) {
callback(error);
});
// Clean up DOM
if (_pendingFonts[fontFace.id].defaultNode) {
document.body.removeChild(_pendingFonts[fontFace.id].defaultNode);
}
if (_pendingFonts[fontFace.id].testNode) {
document.body.removeChild(_pendingFonts[fontFace.id].testNode);
}
// Clean up waiting queue
delete _pendingFonts[fontFace.id];
}
module.exports = {
isFontLoaded: isFontLoaded,
loadFont: _useNativeImpl ? loadFontNative : loadFont
};