Greasy Fork

来自缓存

Greasy Fork is available in English.

思源黑体网页替换脚本

将网页字体替换为思源黑体,资源使用外部注入

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    }

})();