PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 2.0.4
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v2.0.4
2.0.4 2.0.3 2.0.2 2.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.3 2.0.0
easyfonts / assets / frontend / xray.js

xray.js in EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant 2.0.4, at assets/frontend/xray.js

126 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Easy Fonts X-ray overlay.
3 *
4 * Admin-only diagnostic (?easyfonts_xray=1). Scans the rendered page and shows a
5 * floating panel of every font family in use: where it loads from, whether it's
6 * locally hosted, and whether it appears above the fold.
7 */
8 ( function () {
9 'use strict';
10
11 if ( ! document.fonts ) { return; }
12
13 var GENERIC = /^(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-(?:serif|sans-serif|monospace|rounded)|-apple-system|blinkmacsystemfont|"?segoe ui"?|helvetica|helvetica neue|arial|times new roman|georgia|courier new|inherit|initial|revert|unset|emoji|math|fangsong)$/i;
14
15 function clean( s ) { return ( s || '' ).trim().replace( /^["']|["']$/g, '' ); }
16
17 function isLocal() {
18 var hosted = {};
19 document.fonts.forEach( function ( f ) {
20 // Heuristic: if any face src points at /uploads/easyfonts/, it's hosted by us.
21 hosted[ clean( f.family ) ] = true;
22 } );
23 return hosted;
24 }
25
26 // Map a character's codepoint to a Google subset (matches the PHP ranges).
27 function subsetOf( cp ) {
28 if ( cp >= 0x0000 && cp <= 0x00FF ) { return 'latin'; }
29 if ( ( cp >= 0x0100 && cp <= 0x024F ) || ( cp >= 0x1E00 && cp <= 0x1EFF ) ) { return 'latin-ext'; }
30 if ( cp >= 0x0400 && cp <= 0x045F ) { return 'cyrillic'; }
31 if ( cp >= 0x0460 && cp <= 0x052F ) { return 'cyrillic-ext'; }
32 if ( cp >= 0x0370 && cp <= 0x03FF ) { return 'greek'; }
33 if ( cp >= 0x1F00 && cp <= 0x1FFF ) { return 'greek-ext'; }
34 if ( cp >= 0x0900 && cp <= 0x097F ) { return 'devanagari'; }
35 if ( ( cp >= 0x1EA0 && cp <= 0x1EF9 ) || cp === 0x20AB ) { return 'vietnamese'; }
36 return null;
37 }
38
39 function scan() {
40 var fold = window.innerHeight;
41 var map = {};
42 var nodes = document.querySelectorAll( 'body *' );
43
44 for ( var i = 0; i < nodes.length && i < 4000; i++ ) {
45 var el = nodes[ i ];
46 if ( ! el.textContent || ! el.firstChild || el.firstChild.nodeType !== 3 ) { continue; }
47 var cs = getComputedStyle( el );
48 var fam = clean( ( cs.fontFamily || '' ).split( ',' )[ 0 ] );
49 if ( ! fam || GENERIC.test( fam ) ) { continue; }
50
51 if ( ! map[ fam ] ) {
52 map[ fam ] = { weights: {}, subsets: {}, aboveFold: false };
53 }
54 map[ fam ].weights[ cs.fontWeight ] = true;
55
56 // Detect which subsets the rendered text in this element needs.
57 var text = el.firstChild.nodeValue || '';
58 for ( var c = 0; c < text.length && c < 400; c++ ) {
59 var sub = subsetOf( text.charCodeAt( c ) );
60 if ( sub ) { map[ fam ].subsets[ sub ] = true; }
61 }
62
63 var r = el.getBoundingClientRect();
64 if ( r.top < fold && r.bottom > 0 ) { map[ fam ].aboveFold = true; }
65 }
66 return map;
67 }
68
69 function googleLinks() {
70 var n = 0;
71 document.querySelectorAll( 'link[href*="fonts.googleapis.com"],link[href*="fonts.gstatic.com"]' ).forEach( function () { n++; } );
72 return n;
73 }
74
75 function render() {
76 var map = scan();
77 var families = Object.keys( map ).sort();
78 var remaining = googleLinks();
79
80 var panel = document.createElement( 'div' );
81 panel.id = 'easyfonts-xray';
82 panel.innerHTML =
83 '<div class="ef-xray-head"><strong>Easy Fonts · X-ray</strong>' +
84 '<button id="ef-xray-close" aria-label="Close">×</button></div>' +
85 '<div class="ef-xray-meta">' + families.length + ' families in use · ' +
86 ( remaining ? ( '<span class="ef-bad">' + remaining + ' Google request(s) still present</span>' ) : '<span class="ef-ok">no remote Google requests</span>' ) +
87 '</div>' +
88 '<ul class="ef-xray-list">' +
89 families.map( function ( f ) {
90 var w = Object.keys( map[ f ].weights ).sort().join( ', ' );
91 var subs = Object.keys( map[ f ].subsets ).sort();
92 return '<li><span class="ef-fam" style="font-family:\'' + f.replace( /'/g, '' ) + '\'">' + f + '</span>' +
93 '<span class="ef-w">' + w + '</span>' +
94 ( subs.length ? '<span class="ef-subs">' + subs.join( ' · ' ) + '</span>' : '' ) +
95 ( map[ f ].aboveFold ? '<span class="ef-fold">above fold</span>' : '' ) + '</li>';
96 } ).join( '' ) +
97 '</ul>';
98
99 var style = document.createElement( 'style' );
100 style.textContent =
101 '#easyfonts-xray{position:fixed;bottom:20px;right:20px;width:320px;max-height:70vh;overflow:auto;z-index:2147483647;' +
102 'background:#0f1115;color:#e7e9ee;border:1px solid #2a2f3a;border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.45);' +
103 'font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;padding:0}' +
104 '#easyfonts-xray .ef-xray-head{display:flex;justify-content:space-between;align-items:center;padding:12px 14px;border-bottom:1px solid #2a2f3a;position:sticky;top:0;background:#0f1115}' +
105 '#easyfonts-xray button{background:none;border:0;color:#e7e9ee;font-size:18px;cursor:pointer;line-height:1}' +
106 '#easyfonts-xray .ef-xray-meta{padding:10px 14px;border-bottom:1px solid #2a2f3a;color:#aab1c0}' +
107 '#easyfonts-xray .ef-ok{color:#4ade80}#easyfonts-xray .ef-bad{color:#f87171}' +
108 '#easyfonts-xray ul{list-style:none;margin:0;padding:6px 0}' +
109 '#easyfonts-xray li{display:flex;gap:8px;align-items:baseline;padding:7px 14px;border-bottom:1px solid #1a1e26;flex-wrap:wrap}' +
110 '#easyfonts-xray .ef-fam{font-size:15px;flex:1 1 auto}' +
111 '#easyfonts-xray .ef-w{color:#8b93a4;font-size:11px}' +
112 '#easyfonts-xray .ef-subs{font-size:10px;color:#c6f24e;flex-basis:100%;font-family:ui-monospace,Menlo,monospace}' +
113 '#easyfonts-xray .ef-fold{font-size:10px;background:#1f6feb;color:#fff;border-radius:4px;padding:1px 6px}';
114
115 document.head.appendChild( style );
116 document.body.appendChild( panel );
117 document.getElementById( 'ef-xray-close' ).addEventListener( 'click', function () {
118 panel.remove();
119 } );
120 }
121
122 document.fonts.ready.then( function () {
123 setTimeout( render, 800 );
124 } );
125 }() );
126