feat(fonts): 新增六款自托管字族(体积闸门内)(5i T9)
用户拍板「字族扩展都补」。六款按三类各两款落地,全部单页增量 ≤150KB:
小薇(文学·宋体气质)、新晰黑(文学·现代黑)、得意黑(标题)、庆科黄油(标题·圆润)、
马善政毛笔与柳建毛草(书法),许可证均 OFL。
关键取舍:@fontsource 官方中文切片粒度太粗(每片约 33 字、单片 40KB+),一句话命中
7 片即 308–442KB/页,四款全部超闸门;即便用 subset-fonts.py 按站内用字重裁仍剩
205–346KB(笔画类字族单片就 50KB)。故改为从 google/fonts 取原始 TTF,按**字频序
细切**(约 19 字/片、单片 2–12KB),每族 99–100 片、族总量 0.5–1.2MB,浏览器只拉
命中的片。四个 @fontsource 依赖随之移除,package.json 净零变更。
实测单页增量(一句话场景):xiaowei 44.9KB / huangyou 27.1KB / mashanzheng 45.7KB /
liujianmaocao 30.0KB / smiley 27.1KB / neoxihei 30.0KB——全部 PASS;
六族同页共 105KB。
接线(一处漏就静默失效,踩过两次):
- rehype-class-whitelist 放行新类(不放行则 span 的 class 被整条剥掉,页面有字没脸)
- site-content 的 ALLOWED_PAGE_FONTS 与后台排版下拉同源补齐(三处下拉 + hint)
- font-needs 扩六个 key,保证「内容用到才加载」的按需契约继续成立
- global.css 六组规则含 .mom-text 作用域(5h 补的瞬间作用域不能漏)
- typography-classes.js(后台可视化面板单一事实源)35→42 项
- subset-fonts.py 新增 --split-ttf / --slices / --order-from(hwmc 的 --css 用法未动)
覆盖边界(已在 docs 与后台 hint 写明):切片只含站内用字(约 1900 字),
新写生僻字会回落系统字族,需按 docs 命令重切。
验证: 构建 50 页 exit 0;content-lint 51 页 0/0/0;config-check 211 字段/66 hint;
六类逐项 getComputedStyle 首项 = ZCOOL XiaoWei / ZCOOL QingKe HuangYou /
Ma Shan Zheng / Liu Jian Mao Cao / Smiley Sans / LXGW Neo XiHei(6/6);
六族全部出现在 document.fonts;首页字体请求数保持 17(零回归)
Spec: 2026-11-02_blog-5i-fonts-polish (T9)
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
073e124344
commit
9df86cb05b
611 files changed
+962
-9
No files matched your search
@@ -6,6 +6,9 @@ import sansVar from '@fontsource-variable/noto-sans-sc/index.css?url';
|
||||
import mono400 from '@fontsource/jetbrains-mono/400.css?url';
|
||||
import mono700 from '@fontsource/jetbrains-mono/700.css?url';
|
||||
import kuaile from '@fontsource/zcool-kuaile/index.css?url';
|
||||
// 5i T9 字族扩展:四款 @fontsource 新字族**已按站内用字子集化**(原片粗 ~45KB/片,
|
||||
// 一句话命中 7 片就 300-440KB,超 150KB/页闸门);产物落 public/fonts/<family>/,
|
||||
// 由 scripts/build-perf/subset-fonts.py --css 生成,故这里走 public 路径而非 ?url。
|
||||
import serifVar from '@fontsource-variable/noto-serif-sc/index.css?url';
|
||||
import kai from 'lxgw-wenkai-screen-webfont/lxgwwenkaiscreen.css?url';
|
||||
import '../styles/global.css';
|
||||
@@ -93,6 +96,26 @@ const fontNeeds = await fontsForPage(rawPath, track);
|
||||
{fontNeeds.has('huiwen') && (
|
||||
<link rel="preload" as="style" href="/fonts/subset/hwmc/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
<!-- 5i T9:四款 @fontsource 新字族 + 两款手工切片(得意黑/霞鹜新晰黑,脚本:
|
||||
scripts/build-perf/subset-fonts.py --split-ttf,借 hwmc 窗口作字频序) -->
|
||||
{fontNeeds.has('xiaowei') && (
|
||||
<link rel="preload" as="style" href="/fonts/xiaowei/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('huangyou') && (
|
||||
<link rel="preload" as="style" href="/fonts/huangyou/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('mashanzheng') && (
|
||||
<link rel="preload" as="style" href="/fonts/mashanzheng/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('liujianmaocao') && (
|
||||
<link rel="preload" as="style" href="/fonts/liujianmaocao/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('smiley') && (
|
||||
<link rel="preload" as="style" href="/fonts/smiley/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
{fontNeeds.has('neoxihei') && (
|
||||
<link rel="preload" as="style" href="/fonts/neoxihei/fonts.css" onload="this.rel='stylesheet'" />
|
||||
)}
|
||||
<noscript>
|
||||
<link rel="stylesheet" href={sansVar} />
|
||||
<link rel="stylesheet" href={mono400} />
|
||||
@@ -100,6 +123,12 @@ const fontNeeds = await fontsForPage(rawPath, track);
|
||||
{fontNeeds.has('kai') && <link rel="stylesheet" href={kai} />}
|
||||
{fontNeeds.has('kuaile') && <link rel="stylesheet" href={kuaile} />}
|
||||
{fontNeeds.has('huiwen') && <link rel="stylesheet" href="/fonts/subset/hwmc/fonts.css" />}
|
||||
{fontNeeds.has('xiaowei') && <link rel="stylesheet" href="/fonts/xiaowei/fonts.css" />}
|
||||
{fontNeeds.has('huangyou') && <link rel="stylesheet" href="/fonts/huangyou/fonts.css" />}
|
||||
{fontNeeds.has('mashanzheng') && <link rel="stylesheet" href="/fonts/mashanzheng/fonts.css" />}
|
||||
{fontNeeds.has('liujianmaocao') && <link rel="stylesheet" href="/fonts/liujianmaocao/fonts.css" />}
|
||||
{fontNeeds.has('smiley') && <link rel="stylesheet" href="/fonts/smiley/fonts.css" />}
|
||||
{fontNeeds.has('neoxihei') && <link rel="stylesheet" href="/fonts/neoxihei/fonts.css" />}
|
||||
</noscript>
|
||||
<!-- PWA:manifest + 双方案 theme-color(明暗随系统)+ iOS 图标 -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
import { getCollection, getEntry } from 'astro:content';
|
||||
|
||||
/** 可独立加载的字体 CSS 包(sans / mono 常驻,不在此列) */
|
||||
export type FontKey = 'serif' | 'kai' | 'kuaile' | 'huiwen';
|
||||
export type FontKey = 'serif' | 'kai' | 'kuaile' | 'huiwen' | 'xiaowei' | 'huangyou' | 'mashanzheng' | 'liujianmaocao' | 'smiley' | 'neoxihei';
|
||||
|
||||
/**
|
||||
* 类名 → 需要的字体包。
|
||||
@@ -48,6 +48,13 @@ export const FONT_CLASS_NEEDS: Record<string, FontKey[]> = {
|
||||
'font-huiwen': ['huiwen'],
|
||||
'font-kuaile': ['kuaile'],
|
||||
'font-art-kuaile': ['kuaile'], // 同一个 ZCOOL KuaiLe 族
|
||||
// 5i T9 字族扩展六款(2026-11-03):内容里写到才加载对应 CSS
|
||||
'font-xiaowei': ['xiaowei'],
|
||||
'font-huangyou': ['huangyou'],
|
||||
'font-mashanzheng': ['mashanzheng'],
|
||||
'font-liujianmaocao': ['liujianmaocao'],
|
||||
'font-smiley': ['smiley'],
|
||||
'font-neoxihei': ['neoxihei'],
|
||||
'quote-line': ['kai'],
|
||||
'drop-cap': ['serif'],
|
||||
};
|
||||
|
||||
@@ -51,6 +51,13 @@ const ALLOWED = new Set([
|
||||
'drop-cap',
|
||||
'quote-line',
|
||||
'fig-cap',
|
||||
// 5i T9 字族扩展六款(2026-11-03):与 global.css / typography-classes.js 同步增补
|
||||
'font-xiaowei',
|
||||
'font-huangyou',
|
||||
'font-mashanzheng',
|
||||
'font-liujianmaocao',
|
||||
'font-smiley',
|
||||
'font-neoxihei',
|
||||
]);
|
||||
|
||||
export default function rehypeClassWhitelist() {
|
||||
|
||||
@@ -174,6 +174,9 @@ const ALLOWED_PAGE_FONTS = new Set([
|
||||
'font-serif', 'font-kai', 'font-sans', 'font-mono',
|
||||
'font-song', 'font-kai-sys', 'font-hei-sys', 'font-fangsong',
|
||||
'font-huiwen', 'font-kuaile',
|
||||
// 5i T9 字族扩展:页面级排版覆盖(typography.font)也放行这六款
|
||||
'font-xiaowei', 'font-huangyou', 'font-mashanzheng',
|
||||
'font-liujianmaocao', 'font-smiley', 'font-neoxihei',
|
||||
]);
|
||||
const ALLOWED_PAGE_ACCENTS = new Set([
|
||||
'ink-accent', 'ink-accent-soft',
|
||||
@@ -204,6 +207,13 @@ function fontVarFor(cls: string): string {
|
||||
'font-song': 'var(--font-serif)', 'font-kai-sys': 'var(--font-kai)',
|
||||
'font-hei-sys': 'var(--font-sans)', 'font-fangsong': '"FangSong", STFangsong, serif',
|
||||
'font-huiwen': '"WF-Hwmc", var(--font-serif)', 'font-kuaile': '"ZCOOL KuaiLe", var(--font-serif)',
|
||||
// 5i T9 字族扩展:字体族名与 global.css / @font-face 三处完全一致
|
||||
'font-xiaowei': '"ZCOOL XiaoWei", var(--font-serif)',
|
||||
'font-huangyou': '"ZCOOL QingKe HuangYou", var(--font-sans)',
|
||||
'font-mashanzheng': '"Ma Shan Zheng", var(--font-kai)',
|
||||
'font-liujianmaocao': '"Liu Jian Mao Cao", var(--font-kai)',
|
||||
'font-smiley': '"Smiley Sans", var(--font-sans)',
|
||||
'font-neoxihei': '"LXGW Neo XiHei", var(--font-sans)',
|
||||
};
|
||||
return map[cls] ?? 'inherit';
|
||||
}
|
||||
|
||||
@@ -225,6 +225,28 @@ pre code { background: none; border: none; padding: 0; font-size: inherit; }
|
||||
.prose-reading .font-fangsong, .prose-tech .font-fangsong, .rich .font-fangsong, .mom-text .font-fangsong, .prose-body .font-fangsong {
|
||||
font-family: FangSong, STFangsong, "FangSong_GB2312", serif;
|
||||
}
|
||||
/* 5i T9 字族扩展六款(均 OFL / 免费商用且允许 web 嵌入):作用域与上文同款五件套,
|
||||
含 .mom-text(瞬间富文本容器)。字体族名必须与 @font-face 完全一致,
|
||||
否则就是“class 在、字形不到”的静默失效(T1 修过的那类)。
|
||||
四款 @fontsource 自带 unicode-range 切片;得意黑/霞鹜新晰黑 走 public/fonts 手工切片。 */
|
||||
.prose-reading .font-xiaowei, .prose-tech .font-xiaowei, .rich .font-xiaowei, .mom-text .font-xiaowei, .prose-body .font-xiaowei {
|
||||
font-family: 'ZCOOL XiaoWei', var(--font-serif);
|
||||
}
|
||||
.prose-reading .font-huangyou, .prose-tech .font-huangyou, .rich .font-huangyou, .mom-text .font-huangyou, .prose-body .font-huangyou {
|
||||
font-family: 'ZCOOL QingKe HuangYou', var(--font-sans);
|
||||
}
|
||||
.prose-reading .font-mashanzheng, .prose-tech .font-mashanzheng, .rich .font-mashanzheng, .mom-text .font-mashanzheng, .prose-body .font-mashanzheng {
|
||||
font-family: 'Ma Shan Zheng', var(--font-kai);
|
||||
}
|
||||
.prose-reading .font-liujianmaocao, .prose-tech .font-liujianmaocao, .rich .font-liujianmaocao, .mom-text .font-liujianmaocao, .prose-body .font-liujianmaocao {
|
||||
font-family: 'Liu Jian Mao Cao', var(--font-kai);
|
||||
}
|
||||
.prose-reading .font-smiley, .prose-tech .font-smiley, .rich .font-smiley, .mom-text .font-smiley, .prose-body .font-smiley {
|
||||
font-family: 'Smiley Sans', var(--font-sans);
|
||||
}
|
||||
.prose-reading .font-neoxihei, .prose-tech .font-neoxihei, .rich .font-neoxihei, .mom-text .font-neoxihei, .prose-body .font-neoxihei {
|
||||
font-family: 'LXGW Neo XiHei', var(--font-sans);
|
||||
}
|
||||
/* 文学常用:首字下沉(书评开篇)与金句块(引文式的独立强调段) */
|
||||
.prose-reading p.drop-cap::first-letter, .prose-tech p.drop-cap::first-letter, .rich p.drop-cap::first-letter, .mom-text p.drop-cap::first-letter, .prose-body p.drop-cap::first-letter {
|
||||
float: left;
|
||||
|
||||
Reference in new issue
Block a user