perf(fonts): 优化字体加载性能并添加子集化脚本
将 TTF 字体转换为更高效的 WOFF2 格式,添加字体子集化脚本以减小文件大小 优化字体预加载和显示策略,改进无障碍标签
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
字体子集化脚本 - 优化 AoyagiReisho 字体
|
||||||
|
只保留网站实际使用的汉字字符,大幅减小字体文件大小
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.subset import Subsetter, Options
|
||||||
|
except ImportError:
|
||||||
|
print("❌ 缺少 fonttools 库")
|
||||||
|
print("请运行: pip install fonttools brotli")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def create_font_subset(
|
||||||
|
input_font: str,
|
||||||
|
output_font: str,
|
||||||
|
chars_file: str,
|
||||||
|
output_format: str = "woff2"
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
创建字体子集
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_font: 输入字体文件路径
|
||||||
|
output_font: 输出字体文件路径
|
||||||
|
chars_file: 包含所需字符的文本文件
|
||||||
|
output_format: 输出格式 (ttf, woff, woff2)
|
||||||
|
"""
|
||||||
|
print(f"📝 正在读取字符列表: {chars_file}")
|
||||||
|
|
||||||
|
# 读取所需字符
|
||||||
|
with open(chars_file, 'r', encoding='utf-8') as f:
|
||||||
|
chars = set(f.read())
|
||||||
|
|
||||||
|
# 移除空白字符
|
||||||
|
chars = chars - {'\n', '\r', '\t', ' '}
|
||||||
|
|
||||||
|
print(f"✅ 共需保留 {len(chars)} 个字符")
|
||||||
|
|
||||||
|
# 加载字体
|
||||||
|
print(f"📥 正在加载字体: {input_font}")
|
||||||
|
font = TTFont(input_font)
|
||||||
|
|
||||||
|
original_size = os.path.getsize(input_font) / 1024 / 1024
|
||||||
|
print(f"📊 原始字体大小: {original_size:.2f} MB")
|
||||||
|
|
||||||
|
# 创建子集化器
|
||||||
|
options = Options()
|
||||||
|
# 不删除必要的表
|
||||||
|
options.flavor = output_format
|
||||||
|
|
||||||
|
subsetter = Subsetter(options=options)
|
||||||
|
|
||||||
|
# 添加所需字符
|
||||||
|
subsetter.populate(chars)
|
||||||
|
|
||||||
|
# 执行子集化
|
||||||
|
print("⚙️ 正在创建字体子集...")
|
||||||
|
subsetter.subset(font)
|
||||||
|
|
||||||
|
# 保存字体
|
||||||
|
print(f"💾 正在保存字体: {output_font}")
|
||||||
|
font.save(output_font)
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
# 统计结果
|
||||||
|
new_size = os.path.getsize(output_font) / 1024 / 1024
|
||||||
|
reduction = (1 - new_size / original_size) * 100
|
||||||
|
|
||||||
|
print(f"\n✨ 优化完成!")
|
||||||
|
print(f"📊 新字体大小: {new_size:.2f} MB")
|
||||||
|
print(f"📉 减小比例: {reduction:.1f}%")
|
||||||
|
print(f"💾 节省空间: {(original_size - new_size):.2f} MB")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 项目根目录
|
||||||
|
project_root = Path(__file__).parent.parent
|
||||||
|
|
||||||
|
# 输入输出路径
|
||||||
|
input_font = project_root / "public/fonts/AoyagiReisho.ttf"
|
||||||
|
output_dir = project_root / "public/fonts"
|
||||||
|
chars_file = project_root / "scripts/font-chars.txt"
|
||||||
|
|
||||||
|
# 检查文件是否存在
|
||||||
|
if not input_font.exists():
|
||||||
|
print(f"❌ 字体文件不存在: {input_font}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not chars_file.exists():
|
||||||
|
print(f"❌ 字符文件不存在: {chars_file}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# 创建 WOFF2 格式
|
||||||
|
output_woff2 = output_dir / "AoyagiReisho.woff2"
|
||||||
|
print("\n🎨 创建 WOFF2 格式字体 (推荐)")
|
||||||
|
create_font_subset(
|
||||||
|
str(input_font),
|
||||||
|
str(output_woff2),
|
||||||
|
str(chars_file),
|
||||||
|
output_format="woff2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建 TTF 格式 (作为备选)
|
||||||
|
output_ttf = output_dir / "AoyagiReisho-subset.ttf"
|
||||||
|
print("\n🎨 创建 TTF 格式字体 (备选)")
|
||||||
|
create_font_subset(
|
||||||
|
str(input_font),
|
||||||
|
str(output_ttf),
|
||||||
|
str(chars_file),
|
||||||
|
output_format=None
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("✅ 所有字体优化完成!")
|
||||||
|
print("="*60)
|
||||||
|
print(f"\n📁 输出文件:")
|
||||||
|
print(f" - WOFF2: {output_woff2}")
|
||||||
|
print(f" - TTF: {output_ttf}")
|
||||||
|
print(f"\n💡 建议: 在 CSS 中优先使用 WOFF2 格式")
|
||||||
|
print(f" font-display: swap 可确保字体加载时文字可见")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""字体子集化脚本 - 处理有问题的字体文件"""
|
||||||
|
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.subset import Subsetter
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 读取字符
|
||||||
|
with open('scripts/font-chars.txt', 'r', encoding='utf-8') as f:
|
||||||
|
chars = set(f.read()) - {'\n', '\r', '\t', ' '}
|
||||||
|
|
||||||
|
print(f'📝 需要保留的字符数: {len(chars)}')
|
||||||
|
|
||||||
|
# 加载字体
|
||||||
|
input_font = 'public/fonts/AoyagiReisho.ttf'
|
||||||
|
font = TTFont(input_font)
|
||||||
|
original_size = os.path.getsize(input_font) / 1024 / 1024
|
||||||
|
print(f'📊 原始字体大小: {original_size:.2f} MB')
|
||||||
|
|
||||||
|
# 删除有问题的表
|
||||||
|
problematic_tables = ['gasp', 'mort']
|
||||||
|
for table in problematic_tables:
|
||||||
|
if table in font:
|
||||||
|
print(f'⚠️ 删除有问题的表: {table}')
|
||||||
|
del font[table]
|
||||||
|
|
||||||
|
# 创建子集
|
||||||
|
print('⚙️ 正在创建字体子集...')
|
||||||
|
subsetter = Subsetter()
|
||||||
|
subsetter.populate(chars)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subsetter.subset(font)
|
||||||
|
except Exception as e:
|
||||||
|
print(f'⚠️ 子集化过程中出现警告: {e}')
|
||||||
|
print('继续处理...')
|
||||||
|
|
||||||
|
# 保存 WOFF2
|
||||||
|
output_woff2 = 'public/fonts/AoyagiReisho.woff2'
|
||||||
|
print(f'💾 正在保存 WOFF2: {output_woff2}')
|
||||||
|
font.flavor = 'woff2'
|
||||||
|
font.save(output_woff2)
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
# 统计
|
||||||
|
new_size = os.path.getsize(output_woff2) / 1024 / 1024
|
||||||
|
reduction = (1 - new_size / original_size) * 100
|
||||||
|
|
||||||
|
print(f'\n✨ 优化完成!')
|
||||||
|
print(f'📊 新字体大小: {new_size:.2f} MB')
|
||||||
|
print(f'📉 减小比例: {reduction:.1f}%')
|
||||||
|
print(f'💾 节省空间: {(original_size - new_size):.2f} MB')
|
||||||
|
print(f'\n✅ 字体文件已保存到: {output_woff2}')
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""简化的字体子集化脚本"""
|
||||||
|
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.subset import Subsetter
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 读取字符
|
||||||
|
with open('scripts/font-chars.txt', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
chars = set(text) - {'\n', '\r', '\t', ' '}
|
||||||
|
|
||||||
|
print(f'📝 需要保留的字符数: {len(chars)}')
|
||||||
|
|
||||||
|
# 加载字体
|
||||||
|
input_font = 'public/fonts/AoyagiReisho.ttf'
|
||||||
|
font = TTFont(input_font)
|
||||||
|
original_size = os.path.getsize(input_font) / 1024 / 1024
|
||||||
|
print(f'📊 原始字体大小: {original_size:.2f} MB')
|
||||||
|
|
||||||
|
# 创建子集
|
||||||
|
print('⚙️ 正在创建字体子集...')
|
||||||
|
subsetter = Subsetter()
|
||||||
|
subsetter.populate(chars)
|
||||||
|
subsetter.subset(font)
|
||||||
|
|
||||||
|
# 保存 WOFF2
|
||||||
|
output_woff2 = 'public/fonts/AoyagiReisho.woff2'
|
||||||
|
print(f'💾 正在保存 WOFF2: {output_woff2}')
|
||||||
|
font.flavor = 'woff2'
|
||||||
|
font.save(output_woff2)
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
# 统计
|
||||||
|
new_size = os.path.getsize(output_woff2) / 1024 / 1024
|
||||||
|
reduction = (1 - new_size / original_size) * 100
|
||||||
|
|
||||||
|
print(f'\n✨ 优化完成!')
|
||||||
|
print(f'📊 新字体大小: {new_size:.2f} MB')
|
||||||
|
print(f'📉 减小比例: {reduction:.1f}%')
|
||||||
|
print(f'💾 节省空间: {(original_size - new_size):.2f} MB')
|
||||||
|
print(f'\n✅ 字体文件已保存到: {output_woff2}')
|
||||||
+10
-5
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Aoyagi Reisho';
|
font-family: 'Aoyagi Reisho';
|
||||||
src: url('/fonts/AoyagiReisho.ttf') format('truetype');
|
src: url('/fonts/AoyagiReisho.woff2') format('woff2'),
|
||||||
|
url('/fonts/AoyagiReisho.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: swap;
|
||||||
font-stretch: normal;
|
font-stretch: normal;
|
||||||
unicode-range: U+4E00-9FFF, U+3400-4DBF, U+20000-2A6DF, U+2A700-2B73F, U+2B740-2B81F, U+2B820-2CEAF, U+F900-FAFF, U+2F800-2FA1F;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 字体加载优化 - 防止 FOUT */
|
/* 字体加载优化 - 防止 FOUT */
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
--font-sans: var(--font-geist-sans);
|
--font-sans: var(--font-geist-sans);
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
--font-chinese: var(--font-noto-sans-sc);
|
--font-chinese: var(--font-noto-sans-sc);
|
||||||
--font-calligraphy: 'Aoyagi Reisho', var(--font-long-cang), 'Long Cang', var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif;
|
--font-calligraphy: 'Aoyagi Reisho', 'STKaiti', 'KaiTi', 'ZCOOL XiaoWei', serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -179,6 +179,11 @@
|
|||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: var(--font-size-5xl);
|
font-size: var(--font-size-5xl);
|
||||||
|
font-family: 'Aoyagi Reisho', 'STKaiti', 'KaiTi', 'ZCOOL XiaoWei', serif;
|
||||||
|
font-weight: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@@ -341,7 +346,7 @@
|
|||||||
|
|
||||||
/* 青柳隶书体 - 与 Logo 保持一致 */
|
/* 青柳隶书体 - 与 Logo 保持一致 */
|
||||||
.font-calligraphy {
|
.font-calligraphy {
|
||||||
font-family: 'Aoyagi Reisho', var(--font-long-cang), 'Long Cang', var(--font-ma-shan-zheng), 'Ma Shan Zheng', 'ZCOOL XiaoWei', 'STKaiti', 'KaiTi', serif !important;
|
font-family: 'Aoyagi Reisho', 'STKaiti', 'KaiTi', 'ZCOOL XiaoWei', serif !important;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|||||||
+2
-2
@@ -130,9 +130,9 @@ export default function RootLayout({
|
|||||||
{/* 字体预加载优化 */}
|
{/* 字体预加载优化 */}
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/AoyagiReisho.ttf"
|
href="/fonts/AoyagiReisho.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/ttf"
|
type="font/woff2"
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
/>
|
/>
|
||||||
<OrganizationSchema />
|
<OrganizationSchema />
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ function HeaderContent() {
|
|||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="flex items-center group"
|
className="flex items-center group"
|
||||||
aria-label="返回首页"
|
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/logo.svg"
|
src="/logo.svg"
|
||||||
@@ -172,6 +171,7 @@ function HeaderContent() {
|
|||||||
className="h-8 w-auto transition-transform duration-200 group-hover:scale-105"
|
className="h-8 w-auto transition-transform duration-200 group-hover:scale-105"
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
|
<span className="sr-only">返回首页</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<nav className="hidden md:flex items-center gap-1" role="navigation" aria-label="主导航" data-testid="desktop-navigation">
|
<nav className="hidden md:flex items-center gap-1" role="navigation" aria-label="主导航" data-testid="desktop-navigation">
|
||||||
|
|||||||
Reference in New Issue
Block a user