// ==UserScript== // @name 思源黑体网页替换脚本 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 将网页字体替换为思源黑体,资源使用外部注入 // @author Wolfe // @match *://*/* // @grant none // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/557391/%E6%80%9D%E6%BA%90%E9%BB%91%E4%BD%93%E7%BD%91%E9%A1%B5%E6%9B%BF%E6%8D%A2%E8%84%9A%E6%9C%AC.user.js // @updateURL https://update.greasyfork.icu/scripts/557391/%E6%80%9D%E6%BA%90%E9%BB%91%E4%BD%93%E7%BD%91%E9%A1%B5%E6%9B%BF%E6%8D%A2%E8%84%9A%E6%9C%AC.meta.js // ==/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); } })();