Font Substitution

Substitutes arbitrary fonts on webpages.

// ==UserScript==
// @name                 Font Substitution
// @name:ja              フォントを変更します
// @name:ko              글꼴 변경하기
// @name:zh-CN           改变字体
// @name:zh-TW           改變字體
// @namespace            https://greasyfork.org/en/scripts/462060-font-substitution
// @version              1.8.4
// @author               AeamaN
// @description          Substitutes arbitrary fonts on webpages.
// @description:ja       ページ内で指定されている、任意のフォントを変更します。
// @description:ko       페이지 내에서 지정된 임의의 글꼴을 변경합니다.
// @description:zh-CN    改变页面中指定的任何字体。
// @description:zh-TW    改變頁面中指定的任何字體。
// @contributionURL      bitcoin:1DC6uWJWzzwU3iRJDXhUquv6QAYaRvtfFJ
// @match                *://*/*
// @match                file:///*
// @grant                GM_getValue
// @grant                GM_registerMenuCommand
// @grant                GM_setValue
// @run-at               document-idle
// ==/UserScript==

// It's based on the original FontSub by Arithon Kelis.
// Arithon Kelis の FontSub を、参考にしています。


(function () { /* START */


'use strict';


// //////////// Settings //////////// //
// No GUI Settings
// Default values are used
var NO_GUI = false;
// ////////////////////////////////// //

// ///////// Default valuse ///////// //
// Array of fonts you want to substitute
// Empty values are ignored
var ORIG_FONTS = [
	'Courier(?=,|$)',          // 1
	'MS Pゴシック',         // 2
	'MS PGothic',              // 3
	'MS ゴシック',           // 4
	'MS Gothic',               // 5
	'MS UI Gothic',            // 6
	'MS P明朝',             // 7
	'MS PMincho',              // 8
	'MS 明朝',               // 9
	'MS Mincho',               // 10
	'',                        // 11
	''                         // 12
];

// Array of fonts to replace the org fonts sequentially
// To disable it, specify a non-existent font name
var REPLD_FONTS = [
	'Courier New',             // 1
	'Sarasa Gothic J',         // 2
	'Sarasa Gothic J',         // 3
	'Sarasa Fixed J',          // 4
	'Sarasa Fixed J',          // 5
	'Sarasa UI J',             // 6
	'Source Han Serif Medium', // 7
	'Source Han Serif Medium', // 8
	'HanaMinA Regular',        // 9
	'HanaMinA Regular',        // 10
	'',                        // 11
	''                         // 12
];

// Modify fonts weight
// -300      -300
// -200      -200
// -100      -100
//    0         0
//  100      +100
//  200      +200
//  300      +300
//  400      +400
//  500      +500
// 9999    Do Nothing
var MOD_FW = 9999;

// Loop interval(ms)
var INTL = 800;
// ////////////////////////////////// //


var ROOT_E = document.documentElement;
var MYNAME = 'fsub184';

var muts = true;
var observer = new MutationObserver(function (mutations) {
	muts = mutations;
});

var orig_fonts = [];
var repld_fonts = [];
var mod_fw, intl;


function makeDalg() {
	var dalg = document.createElement('div');
	
	dalg.className = 'us-' + MYNAME;
	
	dalg.style.all = 'initial';
	dalg.style.backgroundColor = 'rgb(235, 235, 235)';
	dalg.style.border = '3px outset';
	dalg.style.borderRadius = '1%';
	dalg.style.display = 'none';
	dalg.style.fontFamily = 'monospace';
	dalg.style.fontSize = '12px';
	dalg.style.height = '420px';
	dalg.style.width = '480px';
	dalg.style.paddingLeft = '2px';
	dalg.style.paddingRight = '2px';
	dalg.style.position = 'fixed';
	dalg.style.right = '8px';
	dalg.style.top = '8px';
	dalg.style.zIndex = '2147483647';
	
	var HTML =
		'<span style="all: initial; font-size: 120%; line-height: 210%">' +
		GM_info.script.name + ' ' + GM_info.script.version + ' ' + 'Settings' +
		'</span><br />\n' +
		
		'01.<input type="text" name="orig0" class="top_tf" />' +
		'<span class="top_tf">-&gt;</span><input type="text" name="repld0" class="top_tf" /><br />\n' +
		'02.<input type="text" name="orig1" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld1" class="mid_tf" /><br />\n' +
		'03.<input type="text" name="orig2" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld2" class="mid_tf" /><br />\n' +
		'04.<input type="text" name="orig3" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld3" class="mid_tf" /><br />\n' +
		'05.<input type="text" name="orig4" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld4" class="mid_tf" /><br />\n' +
		'06.<input type="text" name="orig5" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld5" class="mid_tf" /><br />\n' +
		'07.<input type="text" name="orig6" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld6" class="mid_tf" /><br />\n' +
		'08.<input type="text" name="orig7" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld7" class="mid_tf" /><br />\n' +
		'09.<input type="text" name="orig8" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld8" class="mid_tf" /><br />\n' +
		'10.<input type="text" name="orig9" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld9" class="mid_tf" /><br />\n' +
		'11.<input type="text" name="orig10" class="mid_tf" />' +
		'<span class="mid_tf">-&gt;</span><input type="text" name="repld10" class="mid_tf" /><br />\n' +
		'12.<input type="text" name="orig11" class="btm_tf" />' +
		'<span class="btm_tf">-&gt;</span><input type="text" name="repld11" class="btm_tf" /><br />\n' +
		
		'<table class="top_t">\n' +
		'<tr class="top_tr">\n' +
		'<td class="top_td"></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="-300" class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="-200" class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="-100" class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="0"    class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="100"  class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="200"  class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="300"  class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="400"  class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="500"  class="top_r" /></td>\n' +
		'<td class="top_td"><input type="radio" name="mod_fw" value="9999" class="top_r" /></td>\n' +
		'</tr>\n' +
		'<tr class="btm_tr">\n' +
		'<td style="all: initial; font-size: 80%">Weight&nbsp;</td>\n' +
		'<td class="btm_td">-3</td>\n' +
		'<td class="btm_td">-2</td>\n' +
		'<td class="btm_td">-1</td>\n' +
		'<td class="btm_td">&nbsp;0</td>\n' +
		'<td class="btm_td">+1</td>\n' +
		'<td class="btm_td">+2</td>\n' +
		'<td class="btm_td">+3</td>\n' +
		'<td class="btm_td">+4</td>\n' +
		'<td class="btm_td">+5</td>\n' +
		'<td class="btm_td">DN</td>\n' +
		'</tr>\n' +
		'</table>\n' +
		
		'<span style="all: initial; font-size: 100%">' +
		'Loop interval(ms)*&nbsp;' +
		'</span><input type="text" name="intl" size="10" class="top_ti" />' +
		'<span style="all: initial; font-size: 100%">' +
		'&nbsp;*Restart required' +
		'</span><br />\n' +
		
		'<input type="button" class="top_b" value="Cancel" />\n' +
		'<input type="button" class="top_b" value="Set default" />\n' +
		'<input type="button" class="top_b" value="Save & Close" />\n';
	
	dalg.innerHTML = HTML;
	
	for (var e of dalg.querySelectorAll(
		'input.top_tf, input.mid_tf, input.btm_tf, input.top_ti'
	)) {
		e.style.all = 'initial';
		e.style.backgroundColor = 'rgb(255, 255, 255)';
		e.style.fontFamily = 'monospace';
		e.style.fontSize = '100%';
		e.style.marginLeft = '1px';
		e.style.marginRight = '1px';
		e.style.marginTop = '1px';
		e.style.marginBottom = '0px';
		e.style.paddingLeft = '1px';
		e.style.paddingRight = '1px';
		e.style.paddingTop = '1px';
		e.style.paddingBottom = '1px';
	}
	for (var e of dalg.querySelectorAll('input.top_tf, input.mid_tf, input.btm_tf')) {
		e.setAttribute('size', '26');
	}
	
	for (var e of dalg.querySelectorAll('span.top_tf, span.mid_tf, span.btm_tf')) {
		e.style.all = 'initial';
		e.style.fontFamily = 'monospace';
		e.style.fontSize = '100%';
		e.style.marginLeft = '1px';
		e.style.marginRight = '1px';
		e.style.marginTop = '1px';
		e.style.marginBottom = '0px';
	}
	
	for (var e of dalg.querySelectorAll('table.top_t')) {
		e.style.all = 'initial';
		e.style.display = 'table';
		e.style.marginTop = '8px';
		e.style.marginBottom = '8px';
	}
	for (var e of dalg.querySelectorAll('tr.top_tr, tr.btm_tr')) {
		e.style.all = 'initial';
		e.style.display = 'table-row';
	}
	for (var e of dalg.querySelectorAll('td.top_td')) {
		e.style.all = 'initial';
		e.style.display = 'table-cell';
		e.style.marginLeft = '0';
		e.style.marginRight = '0';
		e.style.marginTop = '0';
		e.style.marginBottom = '0';
		e.style.paddingLeft = '8px';
		e.style.paddingRight = '0';
		e.style.paddingTop = '0';
		e.style.paddingBottom = '0';
	}
	for (var e of dalg.querySelectorAll('input.top_r')) {
		e.style.all = 'initial';
		e.style.appearance = 'auto';
		e.style.marginLeft = '0';
		e.style.marginRight = '0';
		e.style.marginTop = '0';
		e.style.marginBottom = '0';
		e.style.paddingLeft = '0';
		e.style.paddingRight = '0';
		e.style.paddingTop = '0';
		e.style.paddingBottom = '0';
	}
	for (var e of dalg.querySelectorAll('td.btm_td')) {
		e.style.all = 'initial';
		e.style.display = 'table-cell';
		e.style.fontFamily = 'monospace';
		e.style.fontSize = '85%';
		e.style.marginLeft = '0';
		e.style.marginRight = '0';
		e.style.marginTop = '0';
		e.style.marginBottom = '0';
		e.style.paddingLeft = '8px';
		e.style.paddingRight = '0';
		e.style.paddingTop = '0';
		e.style.paddingBottom = '0';
		e.style.textAlign = 'center';
	}
	
	for (var e of dalg.querySelectorAll('input.top_b')) {
		e.style.all = 'initial';
		e.style.backgroundColor = 'rgb(190, 190, 190)';
		e.style.borderRadius = '10%';
		e.style.cursor = 'default';
		e.style.fontSize = '110%';
		e.style.marginTop = '10px';
		e.style.marginBottom = '0px';
		e.style.paddingTop = '6px';
		e.style.paddingBottom = '6px';
		e.style.textAlign = 'center';
		e.style.width = '90px';
	}
	
	return dalg;
}


function makeFunc(dalg) {
	dalg.addEventListener(
		'click',
		function (event) { event.stopPropagation(); },
		false
	);
	
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'click',
		function () { dalg.style.display = 'none'; },
		false
	);
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
	
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'click',
		function () {
			for (var i = 0; i < ORIG_FONTS.length; ++i) {
				dalg.querySelector('input[name="orig' + i + '"]').value = ORIG_FONTS[i];
				dalg.querySelector('input[name="repld' + i + '"]').value = REPLD_FONTS[i];
			}
			dalg.querySelector('input[name="mod_fw"][value="' + MOD_FW + '"]').checked = true;
			dalg.querySelector('input[name="intl"]').value = INTL;
		},
		false
	);
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
	
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'click',
		function () {
			for (var i = 0; i < ORIG_FONTS.length; ++i) {
				orig_fonts[i] = dalg.querySelector('input[name="orig' + i + '"]').value;
				repld_fonts[i] = dalg.querySelector('input[name="repld' + i + '"]').value;
				GM_setValue('orig' + i, orig_fonts[i]);
				GM_setValue('repld' + i, repld_fonts[i]);
			}
			for (var e of dalg.querySelectorAll('input[name="mod_fw"]')) {
				if (e.checked) {
					mod_fw = +e.value;
					break;
				}
			}
			GM_setValue('mod_fw', mod_fw);
			intl = +dalg.querySelector('input[name="intl"]').value;
			GM_setValue('intl', intl);
			
			dalg.style.display = 'none'; // style は変化する
		},
		false
	);
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
}


function initGUI() {
	for (var i = 0; i < ORIG_FONTS.length; ++i) {
		if (GM_getValue('orig' + i) === undefined) {
			GM_setValue('orig' + i, ORIG_FONTS[i]);
		} else {
			orig_fonts[i] = GM_getValue('orig' + i);
		}
		
		if (GM_getValue('repld' + i) === undefined) {
			GM_setValue('repld' + i, REPLD_FONTS[i]);
		} else {
			repld_fonts[i] = GM_getValue('repld' + i);
		}
	}
	
	if (GM_getValue('mod_fw') === undefined) {
		GM_setValue('mod_fw', MOD_FW);
	} else {
		mod_fw = GM_getValue('mod_fw');
	}
	
	if (GM_getValue('intl') === undefined) {
		GM_setValue('intl', INTL);
	} else {
		intl = GM_getValue('intl');
	}
	
	var dalg = makeDalg();
	makeFunc(dalg);
	document.body.appendChild(dalg);
	
	GM_registerMenuCommand('Settings', function () {
		if (dalg.style.display == 'none') {
			for (var i = 0; i < ORIG_FONTS.length; ++i) {
				dalg.querySelector('input[name="orig' + i + '"]').value = orig_fonts[i];
				dalg.querySelector('input[name="repld' + i + '"]').value = repld_fonts[i];
			}
			dalg.querySelector('input[name="mod_fw"][value="' + mod_fw + '"]').checked = true;
			dalg.querySelector('input[name="intl"]').value = intl;
			
			dalg.style.display = 'block';
		}
	});
}


function replFF(elm) {
	var ff_before = getComputedStyle(elm, null).fontFamily;
	var ff_after = ff_before;
	if (ff_before) {
		for (var i = 0; i < orig_fonts.length; ++i) {
			if (orig_fonts[i] == '') continue;
			var re = new RegExp(orig_fonts[i], 'gi');
			ff_after = ff_after.replace(re, repld_fonts[i]);
		}
		if (ff_before != ff_after) {
			var sm = muts;
			elm.style.fontFamily = ff_after;
			muts = sm;
		}
	}
}


function modFW(elm) {
	var orig_fw = +getComputedStyle(elm, null).fontWeight;
	var s_mod_fw = mod_fw;
	
	if (!elm.hasAttribute('data-' + MYNAME + '_mfw')) {
		if ((orig_fw <= 100 && mod_fw < 0) || (orig_fw >= 900 && mod_fw > 0)) {
			return;
		} else {
			if (orig_fw + mod_fw < 100) s_mod_fw = 100 - orig_fw;
			if (orig_fw + mod_fw > 900) s_mod_fw = 900 - orig_fw;
		}
	} else {
		orig_fw -= +elm.getAttribute('data-' + MYNAME + '_mfw');
		if ((orig_fw <= 100 && mod_fw < 0) || (orig_fw >= 900 && mod_fw > 0)) {
			s_mod_fw = 0;
		} else {
			if (orig_fw + mod_fw < 100) s_mod_fw = 100 - orig_fw;
			if (orig_fw + mod_fw > 900) s_mod_fw = 900 - orig_fw;
		}
	}
	
	var sm = muts;
	elm.style.fontWeight = orig_fw + s_mod_fw;
	elm.setAttribute('data-' + MYNAME + '_mfw', s_mod_fw);
	muts = sm;
}


function traverse(node) {
	var kids = node.childNodes;
	
	if (node.nodeType == 1) { // 子孫を判別する為
		replFF(node);
		if (mod_fw != 9999) modFW(node);
	}
	
	for (var i = 0; i < kids.length; i++) {
		var kid = kids.item(i);
		if (kid.childNodes.length > 0) traverse(kid);
	}
}


function loop() {
	setTimeout(function () {
		if (muts) {
			muts = null; // 初期値がtrue、変更もしない
			traverse(ROOT_E);
		}
		loop();
	}, intl);
}


for (var i = 0; i < ORIG_FONTS.length; ++i) {
	orig_fonts.push(ORIG_FONTS[i]);
}
for (var i = 0; i < REPLD_FONTS.length; ++i) {
	repld_fonts.push(REPLD_FONTS[i]);
}
mod_fw = MOD_FW;
intl = INTL;

observer.observe(ROOT_E, {
	childList:true, attributes:true, subtree:true, attributeFilter:['style']
});
if(!NO_GUI) initGUI();
loop();


})(); /*  END  */