-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathFont_Analysis.js
285 lines (243 loc) · 12.5 KB
/
Font_Analysis.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
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
// Font Usage Analyzer
// Parses CSS, Resource Timing and DOM to associate WebFont URLs with FontStacks.
// Allows for highlighting of font stacks correlating to WebFont URLs to examine where they are used on the page
// @author Paul Calvano
// @license MIT
// @copyright 2017 Akamai Technologies, Inc.
// Portions of the code are based on work from https://gist.github.com/macbookandrew/f33dbbc0aa582d0515919dc5fb95c00a/
// @license MIT
// @copyright AndrewRMinion Design
(function() {
var startTime = new Date().getTime();
var DEBUG = false;
// Function to resolve relative URLs by creating a HTML element and using the browser to resolve it for us...
// @from http://ecmanaut.blogspot.com/2012/10/absolute-url-from-relative-url-and.html
// @license MIT
// @copyright Johan Sundström
function resolve(url, base_url) {
var doc = document
, old_base = doc.getElementsByTagName('base')[0]
, old_href = old_base && old_base.href
, doc_head = doc.head || doc.getElementsByTagName('head')[0]
, our_base = old_base || doc_head.appendChild(doc.createElement('base'))
, resolver = doc.createElement('a')
, resolved_url
;
our_base.href = base_url || '';
resolver.href = url;
resolved_url = resolver.href; // browser magic at work here
if (old_base) old_base.href = old_href;
else doc_head.removeChild(our_base);
return resolved_url;
}
// Highlight elements with the specified style
window.highlightInPage = function(styleId) {
var thisNode,
allNodes = document.body.getElementsByTagName('*'),
nodes = document.body.querySelectorAll('[data-style-id="' + styleId + '"]');
for (var i = 0; i < allNodes.length; i++) { // remove previous highlights
allNodes[i].classList.remove('style-highlight');
}
for (var i = 0; i < nodes.length; i++) { // add highlight to specified nodes
thisNode = nodes[i];
thisNode.classList.add('style-highlight');
}
}
// Clear all highlights
function clearHighlights() {
highlightInPage();
}
// Add blank stylesheet for highlight rule
var sheet = (function () {
var style = document.createElement("style"); // Create the <style> tag
style.appendChild(document.createTextNode("")); // WebKit hack :(
document.head.appendChild(style); // Add the <style> element to the page
return style.sheet;
})();
// Add specified CSS rule to the specified stylesheet
function addCSSRule(sheet, selector, rules, index = 0) {
if ("insertRule" in sheet) {
sheet.insertRule(selector + "{" + rules + "}", index);
} else if ("addRule" in sheet) {
sheet.addRule(selector, rules, index);
}
}
// add a yellow background to highlighted elements
addCSSRule(sheet, ".style-highlight", "background-color: yellow");
// First Pass - Look for Stylesheets w/o any cssRules. Assume a CORS issue and send a crossorigin request so we can parse some rules..
var fetchedCSS = 0;
if (document.styleSheets) {
for (var s = 0; s < document.styleSheets.length; s++) { // Loop though each StyleSheet
if (DEBUG) console.log("Checking StyleSheet # : " + s + ", " + document.styleSheets[s].href);
if (!document.styleSheets[s].cssRules) {
if (DEBUG) console.log("StyleSheet # : " + s + " No Rules. Fetching another... ");
var link = document.createElement("link");
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = document.styleSheets[s].href;
link.crossOrigin = "anonymous";
link.addEventListener('load', decrement)
link.addEventListener('error', decrement)
document.getElementsByTagName('head')[0].appendChild(link);
fetchedCSS++;
}
}
}
function decrement() {
fetchedCSS--;
checkReady();
}
function checkReady() {
fetchedCSS === 0 && secondPass();
}
checkReady();
function secondPass() {
// Second Pass - Parse all Stylesheets and attempt to find Font Files. Associate Font Files with Font-Family, Font-Weight, and Font-Style.
var fontCount = 0; // Number of fonts we've found.
var fonts = []; // Array for storing Font information.
if (document.styleSheets) {
for (var s = 0; s < document.styleSheets.length; s++) { // Loop though each StyleSheet
if (DEBUG) console.log("Parsing StyleSheet # : " + s + ", " + document.styleSheets[s].href);
if (document.styleSheets[s].cssRules) {
var cssRules = document.styleSheets[s].cssRules.length; // Loop trhough each CSS Rule
if (DEBUG) console.log("StyleSheet # : " + s + " Rule Count = " + cssRules);
for (var r = 0; r < cssRules; r++) {
var splitPattern = /url\(.*?\)/g;
var urls = document.styleSheets[s].cssRules[r].cssText.match(splitPattern); // Extract URLs
// If we have a WebFont, then loop through the extracted URLs
if (document.styleSheets[s].cssRules[r].cssText.indexOf('@font-face') != -1 && urls) {
if (DEBUG) console.log(document.styleSheets[s].cssRules[r].cssText);
for (var f = 0; f < urls.length; f++) {
// Remove the url(" and ") part of the CSS rule.
var furl = urls[f].replace("url(\"", "").replace("\")", "");
furl = resolve(furl, document.styleSheets[s].href); // Create a HTML element to resolve the fully qualified URL based.
// Match the fully qualified URLs against Resource Timing data to confirm it was loaded by the browser
if (performance.getEntriesByName(furl).length > 0) {
// Add Font Information to the fonts array
fonts[fontCount] = {};
fonts[fontCount]['font-family'] = document.styleSheets[s].cssRules[r].style.fontFamily;
fonts[fontCount]['font-weight'] = document.styleSheets[s].cssRules[r].style.fontWeight || "normal";
fonts[fontCount]['font-style'] = document.styleSheets[s].cssRules[r].style.fontStyle || "normal";
fonts[fontCount]['transferSize'] = performance.getEntriesByName(furl)[0].transferSize
fonts[fontCount]['url'] = furl;
if (DEBUG) console.log("Found " + furl + "\tfont-family: " + fonts[fontCount]['font-family'] + "\tfont-weight: " + fonts[fontCount]['font-weight'] + "\tfont-style: " + fonts[fontCount]['font-style']);
fontCount++;
// In case font is named with a weight number (400/700) and referenced in the DOM with a name (normal/bold)
if (document.styleSheets[s].cssRules[r].style.fontWeight == 400) {
fonts[fontCount] = {};
fonts[fontCount]['font-weight'] = "normal";
fonts[fontCount]['font-family'] = document.styleSheets[s].cssRules[r].style.fontFamily;
fonts[fontCount]['font-style'] = document.styleSheets[s].cssRules[r].style.fontStyle || "normal";
fonts[fontCount]['transferSize'] = performance.getEntriesByName(furl)[0].transferSize
fonts[fontCount]['url'] = furl;
fontCount++;
}
if (document.styleSheets[s].cssRules[r].style.fontWeight == 700) {
fonts[fontCount] = {};
fonts[fontCount]['font-weight'] = "bold";
fonts[fontCount]['font-family'] = document.styleSheets[s].cssRules[r].style.fontFamily;
fonts[fontCount]['font-style'] = document.styleSheets[s].cssRules[r].style.fontStyle || "normal";
fonts[fontCount]['transferSize'] = performance.getEntriesByName(furl)[0].transferSize
fonts[fontCount]['url'] = furl;
fontCount++;
}
}
}
}
}
}
}
}
// At this point the fonts[] array will contain an index for each font file
if (DEBUG) console.log("Done Parsing CSS");
if (DEBUG) console.log(fonts);
if (DEBUG) console.log("Second Pass: Parse All DOM Elements");
// Third Pass - Parse all DOM elements and find computed sytles. Correllate fonts[] array to computed styles
var style, ffamily, fweight, fsize, fstyle;
var styleId;
var allStyles = [];
var nodes = document.body.getElementsByTagName('*');
var thisNode;
for (var i = 0; i < nodes.length; i++) { // Loop through all Elements
thisNode = nodes[i];
if (thisNode.style) {
// Identify Font-Family, Font-Weight, and Font-Style.
styleId = '#' + (thisNode.id || thisNode.nodeName + '(' + i + ')');
ffamily = thisNode.style.fontFamily || getComputedStyle(thisNode, '')['font-family'];
fweight = thisNode.style.fontWeight || getComputedStyle(thisNode, '')['font-weight'];
fstyle = thisNode.style.fontStyle || getComputedStyle(thisNode, '')['font-style'];
var urlFound = 0;
for (j = 0; j < fonts.length; j++) { // Loop through Font array we built earlier
if (ffamily.indexOf(fonts[j]['font-family']) >= 0) { // Did we find one of the font URLs?
if (DEBUG) console.log(ffamily + "\tMatched\t" + fonts[j]['font-family'] + "\t" + fonts[j]['font-weight'] + "\t" + fonts[j]['font-style']);
if (DEBUG) console.log(ffamily + "\t" + fweight + "\t" + fstyle + "\t" + fonts[j]['url']);
if (fweight == fonts[j]['font-weight'] && fstyle == fonts[j]['font-style']) {
style = ffamily + "\t" + fweight + "\t" + fstyle + "\t" + fonts[j]['url'] + "\t" + fonts[j]['transferSize']; // Matched a Font File w/ a Computed Style
urlFound++;
}
}
}
}
// Get element's style
if (style) {
if (allStyles.indexOf(style) == -1) {
allStyles.push(style);
}
thisNode.dataset.styleId = allStyles.indexOf(style);
}
// Get element:before's style
ffamily = thisNode.style.fontFamily || getComputedStyle(thisNode, ':before')['font-family'];
fweight = thisNode.style.fontWeight || getComputedStyle(thisNode, ':before')['font-weight'];
fstyle = thisNode.style.fontStyle || getComputedStyle(thisNode, ':before')['font-style'];
var urlFound = 0;
for (j = 0; j < fonts.length; j++) {
if (ffamily.indexOf(fonts[j]['font-family']) >= 0 && fweight == fonts[j]['font-weight'] && fstyle == fonts[j]['font-style']) {
style = ffamily + "\t" + fweight + "\t" + fstyle + "\t" + fonts[j]['url'] + "\t" + fonts[j]['transferSize'];
urlFound++;
}
}
if (style) {
if (allStyles.indexOf(style) == -1) {
allStyles.push(style);
}
// add data-attribute with key for allStyles array
thisNode.dataset.styleId = allStyles.indexOf(style);
}
// get element:after's style
ffamily = thisNode.style.fontFamily || getComputedStyle(thisNode, ':after')['font-family'];
fweight = thisNode.style.fontWeight || getComputedStyle(thisNode, ':after')['font-weight'];
fstyle = thisNode.style.fontStyle || getComputedStyle(thisNode, ':after')['font-style'];
var urlFound = 0;
for (j = 0; j < fonts.length; j++) {
if (ffamily.indexOf(fonts[j]['font-family']) >= 0 && fweight == fonts[j]['font-weight'] && fstyle == fonts[j]['font-style']) {
style = ffamily + "\t" + fweight + "\t" + fstyle + "\t" + fonts[j]['url'] + "\t" + fonts[j]['transferSize'];
urlFound++;
}
}
if (style) {
if (allStyles.indexOf(style) == -1) {
allStyles.push(style);
}
// add data-attribute with key for allStyles array
thisNode.dataset.styleId = allStyles.indexOf(style);
}
}
// At this point the allStyles[] array will contain a list of all unique font stacks on the current page, and contain references to the font URLs
var endTime = new Date().getTime();
console.log("Execution Time: " + (endTime - startTime) + " ms");
// Object for Font Stack Info to Display in Console Table
function Row(data) {
this.FontStack = data[0];
this.Weight = data[1];
this.Style = data[2];
this.WebFont_URL = data[3];
this.TransferSize = data[4];
}
// Output Font Stacks in Console Table
var tbl = [];
for (i = 0; i < allStyles.length; i++)
tbl[i] = new Row(allStyles[i].split('\t'));
console.table(tbl)
console.log("To Highlight a Font, enter the command: highlightInPage(n);\n where 'n' is the ID of the font package from the fontStacksInUse array");
}
})();