Greasy Fork is available in English.
将网页字体替换为思源黑体,资源使用外部注入
// ==UserScript==
// @name 思源黑体网页替换脚本
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 将网页字体替换为思源黑体,资源使用外部注入
// @author Wolfe
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ======================================================================
// 1. CSS部分: 定义所有语言变体的字体栈
// ======================================================================
const cssDefinition = `
/* 注入所有语言变体的网络字体 (如果本地没有) */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Sans+TC:wght@300;400;500;700&family=Noto+Sans+HK:wght@300;400;500;700&family=Noto+Sans+JP:wght@300;400;500;700&family=Noto+Sans+KR:wght@300;400;500;700&family=Noto+Serif+SC:wght@300;400;500;700&family=Noto+Serif+TC:wght@300;400;500;700&family=Noto+Serif+JP:wght@300;400;500;700&family=Noto+Serif+KR:wght@300;400;500;700&display=swap');
@font-face {
font-family: 'Maple Mono';
src: local('Maple Mono NF'), local('Maple Mono'), url('https://cdn.jsdelivr.net/npm/@alphardex/maple-font@latest/dist/MapleMono-NF-Regular.woff2') format('woff2');
font-weight: normal; font-style: normal;
}
:host, :root {
/* 为每种语言定义独立的字体栈 */
--font-sans-sc: 'Noto Sans SC', 'Source Han Sans CN', sans-serif;
--font-sans-tc: 'Noto Sans TC', 'Source Han Sans TC', 'Noto Sans HK', sans-serif;
--font-sans-jp: 'Noto Sans JP', 'Source Han Sans JP', sans-serif;
--font-sans-kr: 'Noto Sans KR', 'Source Han Sans KR', sans-serif;
--font-serif-sc: 'Noto Serif SC', 'Source Han Serif CN', serif;
--font-serif-tc: 'Noto Serif TC', 'Source Han Serif TC', serif;
--font-serif-jp: 'Noto Serif JP', 'Source Han Serif JP', serif;
--font-serif-kr: 'Noto Serif KR', 'Source Han Serif KR', serif;
--font-mono: 'Maple Mono', 'Sarasa Gothic SC', 'Fira Code', 'Menlo', 'Consolas', monospace;
}
`;
const styleElement = document.createElement('style');
styleElement.textContent = cssDefinition;
document.documentElement.appendChild(styleElement);
// ======================================================================
// 2. JavaScript部分: 语言感知与强制执行引擎 (此部分无需改动)
// ======================================================================
const processedElements = new WeakSet();
const processedRoots = new WeakSet();
// Unicode 特征检测正则表达式
const KOREAN_REGEX = /[\uAC00-\uD7A3]/; // 韩文谚文
const JAPANESE_REGEX = /[\u3040-\u309F\u30A0-\u30FF]/; // 日文平假名、片假名
/**
* 检测元素的语言 (lang属性优先,字符特征备用)
* @param {HTMLElement} element
* @returns {'sc'|'tc'|'jp'|'kr'}
*/
function detectLanguage(element) {
// 1. 语义检测: 检查 lang 属性
const langAttr = element.closest('[lang]')?.lang.toLowerCase();
if (langAttr) {
if (langAttr.startsWith('zh-cn') || langAttr.startsWith('zh-sg')) return 'sc';
if (langAttr.startsWith('zh-tw') || langAttr.startsWith('zh-hk') || langAttr.startsWith('zh-hant')) return 'tc';
if (langAttr.startsWith('ja')) return 'jp';
if (langAttr.startsWith('ko')) return 'kr';
}
// 2. 特征检测: 分析文本内容
const text = element.textContent;
if (text) {
if (KOREAN_REGEX.test(text)) return 'kr';
if (JAPANESE_REGEX.test(text)) return 'jp';
}
// 3. 默认/回退: 繁体中文(更广泛)或简体中文
// 在无法明确判断时,默认使用简体中文作为基础
return 'sc';
}
const PROTECTED_TAGS = new Set(['i', 'em', 'svg', 'path', 'RGSb', 'button', 'script', 'style', 'link', 'meta', 'noscript']);
const CODE_TAGS = new Set(['pre', 'code', 'kbd', 'samp']);
const HEADING_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
const ICON_CLASS_REGEX = /icon|fa-|fa\s|glyph|emoji|symbol|octicon/i;
function forceStyleOnElement(element) {
if (!element?.style || processedElements.has(element)) return;
const tagName = element.tagName.toLowerCase();
if (PROTECTED_TAGS.has(tagName) || ICON_CLASS_REGEX.test(element.className)) {
processedElements.add(element); return;
}
const style = getComputedStyle(element);
let targetFontFamily = null;
if (CODE_TAGS.has(tagName)) {
targetFontFamily = style.getPropertyValue('--font-mono').trim();
} else {
const lang = detectLanguage(element);
const isHeading = HEADING_TAGS.has(tagName);
const fontType = isHeading ? 'serif' : 'sans';
// 根据语言和类型,动态构建并获取对应的CSS变量
const variableName = `--font-${fontType}-${lang}`;
targetFontFamily = style.getPropertyValue(variableName).trim();
}
if (targetFontFamily && element.style.fontFamily !== targetFontFamily) {
element.style.setProperty('font-family', targetFontFamily, 'important');
}
processedElements.add(element);
}
function traverseAndApply(rootNode) {
if (!rootNode || processedRoots.has(rootNode)) return;
if (rootNode instanceof ShadowRoot) {
const style = document.createElement('style');
style.textContent = cssDefinition;
rootNode.appendChild(style);
}
processedRoots.add(rootNode);
rootNode.querySelectorAll('*').forEach(el => {
forceStyleOnElement(el);
if (el.shadowRoot) {
traverseAndApply(el.shadowRoot);
}
});
}
// ======================================================================
// 3. 执行与监听 (此部分无需改动)
// ======================================================================
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
traverseAndApply(node);
}
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// 初始执行,确保页面加载时静态内容被处理
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => traverseAndApply(document.body));
} else {
traverseAndApply(document.body);
}
})();