Greasy Fork is available in English.
该脚本允许你将所有网页的字体替换为你本地的任意字体
当前为
// ==UserScript==
// @name 网页字体替换
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 该脚本允许你将所有网页的字体替换为你本地的任意字体
// @author Kyurin
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @noframes
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
if (window.top !== window.self) return;
const CONFIG = {
CHUNK_SIZE: 1024 * 1024,
DB_PREFIX: "FONT_DATA_",
META_KEY: "FONT_META",
CUSTOM_FAMILY: "UserLocalFont"
};
function injectGlobalStyles(blobUrl) {
let css = "";
// 1. Font Name Hijacking (劫持列表)
const hijackList = [
// X (Twitter)
"TwitterChirp", "TwitterChirpExtendedHeavy", "Chirp",
// ArXiv & Academic
"Latin Modern Roman", "Computer Modern", "LinLibertine", "Lucida Grande",
// Modern UI
"Inter", "Inter var", "Inter Tight",
"Google Sans", "Google Sans Text",
"Roboto", "San Francisco", "Segoe UI",
"system-ui", "ui-sans-serif", "-apple-system", "BlinkMacSystemFont", "sans-serif",
// Web Standards
"Helvetica Neue", "Helvetica", "Arial", "Verdana", "Tahoma",
"Open Sans", "Fira Sans", "Ubuntu",
// CJK
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
"Heiti SC", "SimHei", "SimSun", "Noto Sans SC", "Source Han Sans SC",
// Reddit & Others
"IBM Plex Sans", "Reddit Sans", "Noto Sans"
];
hijackList.forEach(name => {
css += `@font-face { font-family: '${name}'; src: url('${blobUrl}'); font-display: swap; }`;
});
css += `@font-face { font-family: '${CONFIG.CUSTOM_FAMILY}'; src: url('${blobUrl}'); font-display: swap; }`;
// 2. Tag-based & Attribute-based Overrides (Reddit 修复核心)
const targetSelectors = [
// 基础标签
"body", "p", "article", "section", "blockquote",
"h1", "h2", "h3", "h4", "h5", "h6",
"li", "dt", "dd", "th", "td",
"b", "strong",
// 导航与链接 (针对 X 的侧边栏)
"nav",
"[role='link']",
"[role='button']",
"[role='menuitem']",
// 文本特征属性 (X 的推文和菜单大量使用 dir="auto")
"[dir='auto']",
"[dir='ltr']",
"[lang]"
];
css += `
${targetSelectors.join(", ")} {
font-family: "${CONFIG.CUSTOM_FAMILY}", "TwitterChirp", "Inter", "Microsoft YaHei", sans-serif !important;
}
input, textarea, select {
font-family: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
}
`;
// 3. Bilibili Subtitle Fix (新增:B站字幕修复)
css += `
.bpx-player-subtitle-panel-text,
.bpx-player-subtitle-wrap span,
.bilibili-player-video-subtitle {
font-family: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
}
`;
// 4. CSS Variables Injection
css += `
:root, html, body {
--text-font-family: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
--font-family-twitter: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
--font-sans: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
--font-serif: "${CONFIG.CUSTOM_FAMILY}", serif !important;
}
.ltx_text, .ltx_title, .ltx_abstract, .ltx_font_bold {
font-family: "${CONFIG.CUSTOM_FAMILY}", sans-serif !important;
}
`;
// 5. Exclusion & Protection (图标与代码保护)
const monoFonts = [
"monospace", "ui-monospace", "Consolas", "Courier New", "Menlo",
"Monaco", "Space Mono", "Roboto Mono", "Fira Code", "JetBrains Mono"
];
monoFonts.forEach(name => {
css += `@font-face { font-family: '${name}'; src: local('monospace'), local('Courier New'); }`;
});
css += `
pre, code, kbd, samp, .monaco-editor, .code-block, textarea.code {
font-family: "Space Mono", "Consolas", monospace !important;
font-variant-ligatures: none;
}
`;
// Icon Protection (SVGs and Icon Fonts)
css += `
[class*="material-symbols"], [class*="material-icons"],
.material-icons, i, em, .icon,
[class*="icon"], [class*="fa-"], [class*="fas"], [class*="fab"],
b[class*="icon"], strong[class*="icon"],
.ltx_icon, .ltx_svg_icon,
/* X (Twitter) 图标保护 */
svg, svg * {
font-family: 'Material Symbols Outlined', 'Material Icons', FontAwesome, initial !important;
font-weight: normal;
font-style: normal;
}
/* Math Protection */
.MathJax, .MathJax *, .mjx-container, .mjx-container *,
.ltx_Math, .ltx_equation, .ltx_equation *, math, math * {
font-family: "Latin Modern Math", serif !important;
}
`;
if (typeof GM_addStyle !== 'undefined') {
GM_addStyle(css);
} else {
const styleEl = document.createElement('style');
styleEl.innerHTML = css;
document.head.appendChild(styleEl);
}
setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
}
const Storage = {
save: function(file) {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
const base64 = e.target.result.split(',')[1];
const totalChunks = Math.ceil(base64.length / CONFIG.CHUNK_SIZE);
this.clear();
try {
for (let i = 0; i < totalChunks; i++) {
GM_setValue(`${CONFIG.DB_PREFIX}${i}`, base64.slice(i * CONFIG.CHUNK_SIZE, (i + 1) * CONFIG.CHUNK_SIZE));
}
GM_setValue(CONFIG.META_KEY, { name: file.name, type: file.type, totalChunks: totalChunks });
alert("✅ 字体上传成功。");
location.reload();
} catch (err) {
alert("❌ 保存失败:空间不足。");
}
};
},
load: function() {
return new Promise((resolve, reject) => {
const meta = GM_getValue(CONFIG.META_KEY);
if (!meta) { resolve(null); return; }
setTimeout(() => {
try {
const chunks = [];
for (let i = 0; i < meta.totalChunks; i++) {
const chunk = GM_getValue(`${CONFIG.DB_PREFIX}${i}`);
if (chunk) chunks.push(chunk);
}
if (chunks.length !== meta.totalChunks) throw new Error("Corrupted data");
// 兼容处理:尝试使用 fetch 转换 Base64 (比 atob 更稳定支持中文大文件)
fetch(`data:${meta.type};base64,${chunks.join('')}`)
.then(res => res.blob())
.then(blob => resolve(blob))
.catch(() => {
// 降级回退到旧的解码方式
const byteStr = atob(chunks.join(''));
const bytes = new Uint8Array(byteStr.length);
for (let i = 0; i < byteStr.length; i++) bytes[i] = byteStr.charCodeAt(i);
resolve(new Blob([bytes], {type: meta.type}));
});
} catch (e) { reject(e); }
}, 0);
});
},
clear: function() {
GM_listValues().forEach(k => {
if (k.startsWith(CONFIG.DB_PREFIX) || k === CONFIG.META_KEY) GM_deleteValue(k);
});
}
};
function init() {
GM_registerMenuCommand("📂 上传字体文件", () => {
const input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
input.accept = ".ttf,.otf,.woff,.woff2";
input.onchange = e => { if(e.target.files[0]) Storage.save(e.target.files[0]); };
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
});
GM_registerMenuCommand("🗑️ 恢复默认", () => {
if(confirm("确定恢复默认吗?")) { Storage.clear(); location.reload(); }
});
Storage.load().then(blob => {
if(blob) injectGlobalStyles(URL.createObjectURL(blob));
}).catch(e => console.error("FontLoader Error:", e));
}
init();
})();