Optimize font size and add more fonts

This commit is contained in:
2025-09-24 00:44:41 +08:00
parent e536cdd9ba
commit 39ee2d14f3
76 changed files with 839 additions and 432 deletions

View File

@@ -0,0 +1,254 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
鸿蒙字体压缩工具
使用 fonttools 库压缩 TTF 字体文件,减小文件大小
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
from typing import List, Tuple
def check_dependencies():
"""检查必要的依赖是否已安装"""
missing_packages = []
# 检查 fonttools
try:
import fontTools
except ImportError:
missing_packages.append('fonttools')
# 检查 brotli
try:
import brotli
except ImportError:
missing_packages.append('brotli')
# 检查 pyftsubset 命令是否可用
try:
result = subprocess.run(['pyftsubset', '--help'], capture_output=True, text=True)
if result.returncode != 0:
missing_packages.append('fonttools[subset]')
except FileNotFoundError:
if 'fonttools' not in missing_packages:
missing_packages.append('fonttools[subset]')
if missing_packages:
print(f"缺少必要的依赖包: {', '.join(missing_packages)}")
print("请运行以下命令安装:")
print(f"pip install {' '.join(missing_packages)}")
return False
return True
def get_file_size(file_path: str) -> int:
"""获取文件大小(字节)"""
return os.path.getsize(file_path)
def format_file_size(size_bytes: int) -> str:
"""格式化文件大小显示"""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.2f} KB"
else:
return f"{size_bytes / (1024 * 1024):.2f} MB"
def compress_font(input_path: str, output_path: str, compression_level: str = "basic") -> bool:
"""
压缩单个字体文件
Args:
input_path: 输入字体文件路径
output_path: 输出字体文件路径
compression_level: 压缩级别 ("basic", "medium", "aggressive")
Returns:
bool: 压缩是否成功
"""
try:
# 基础压缩参数
base_args = [
"pyftsubset", input_path,
"--output-file=" + output_path,
"--flavor=woff2", # 输出为 WOFF2 格式,压缩率更高
"--with-zopfli", # 使用 Zopfli 算法进一步压缩
]
# 根据压缩级别设置不同的参数
if compression_level == "basic":
# 基础压缩:保留常用字符和功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F,U+2070-209F,U+20A0-20CF", # 基本拉丁字符、标点符号等
"--layout-features=*", # 保留所有布局特性
"--glyph-names", # 保留字形名称
"--symbol-cmap", # 保留符号映射
"--legacy-cmap", # 保留传统字符映射
"--notdef-glyph", # 保留 .notdef 字形
"--recommended-glyphs", # 保留推荐字形
"--name-IDs=*", # 保留所有名称ID
"--name-legacy", # 保留传统名称
]
elif compression_level == "medium":
# 中等压缩:移除一些不常用的功能
args = base_args + [
"--unicodes=U+0020-007F,U+00A0-00FF,U+2000-206F", # 减少字符范围
"--layout-features=kern,liga,clig", # 只保留关键布局特性
"--no-glyph-names", # 移除字形名称
"--notdef-glyph",
"--name-IDs=1,2,3,4,5,6", # 只保留基本名称ID
]
else: # aggressive
# 激进压缩:最大程度减小文件大小
args = base_args + [
"--unicodes=U+0020-007F", # 只保留基本ASCII字符
"--no-layout-features", # 移除所有布局特性
"--no-glyph-names", # 移除字形名称
"--no-symbol-cmap", # 移除符号映射
"--no-legacy-cmap", # 移除传统映射
"--notdef-glyph",
"--name-IDs=1,2", # 只保留最基本的名称
"--desubroutinize", # 去子程序化可能减小CFF字体大小
]
# 执行压缩命令
result = subprocess.run(args, capture_output=True, text=True)
if result.returncode == 0:
return True
else:
print(f"压缩失败: {result.stderr}")
return False
except Exception as e:
print(f"压缩过程中出现错误: {str(e)}")
return False
def find_font_files(directory: str) -> List[str]:
"""查找目录中的所有字体文件"""
font_extensions = ['.ttf', '.otf', '.woff', '.woff2']
font_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if any(file.lower().endswith(ext) for ext in font_extensions):
font_files.append(os.path.join(root, file))
return font_files
def compress_fonts_batch(font_directory: str, compression_level: str = "basic"):
"""
批量压缩字体文件
Args:
font_directory: 字体文件目录
compression_level: 压缩级别
"""
if not os.path.exists(font_directory):
print(f"错误: 目录 {font_directory} 不存在")
return
# 查找所有字体文件
font_files = find_font_files(font_directory)
if not font_files:
print("未找到字体文件")
return
print(f"找到 {len(font_files)} 个字体文件")
print(f"压缩级别: {compression_level}")
print(f"压缩后的文件将与源文件放在同一目录,扩展名为 .woff2")
print("-" * 60)
total_original_size = 0
total_compressed_size = 0
successful_compressions = 0
for i, font_file in enumerate(font_files, 1):
print(f"[{i}/{len(font_files)}] 处理: {os.path.basename(font_file)}")
# 获取原始文件大小
original_size = get_file_size(font_file)
total_original_size += original_size
# 生成输出文件名(保持原文件名,只改变扩展名)
file_dir = os.path.dirname(font_file)
base_name = os.path.splitext(os.path.basename(font_file))[0]
output_file = os.path.join(file_dir, f"{base_name}.woff2")
# 压缩字体
if compress_font(font_file, output_file, compression_level):
if os.path.exists(output_file):
compressed_size = get_file_size(output_file)
total_compressed_size += compressed_size
successful_compressions += 1
# 计算压缩率
compression_ratio = (1 - compressed_size / original_size) * 100
print(f" ✓ 成功: {format_file_size(original_size)}{format_file_size(compressed_size)} "
f"(压缩 {compression_ratio:.1f}%)")
else:
print(f" ✗ 失败: 输出文件未生成")
else:
print(f" ✗ 失败: 压缩过程出错")
print()
# 显示总结
print("=" * 60)
print("压缩完成!")
print(f"成功压缩: {successful_compressions}/{len(font_files)} 个文件")
if successful_compressions > 0:
total_compression_ratio = (1 - total_compressed_size / total_original_size) * 100
print(f"总大小: {format_file_size(total_original_size)}{format_file_size(total_compressed_size)}")
print(f"总压缩率: {total_compression_ratio:.1f}%")
print(f"节省空间: {format_file_size(total_original_size - total_compressed_size)}")
def main():
"""主函数"""
print("鸿蒙字体压缩工具")
print("=" * 60)
# 检查依赖
if not check_dependencies():
return
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 设置默认字体目录
font_directory = current_dir
print(f"字体目录: {font_directory}")
# 让用户选择压缩级别
print("\n请选择压缩级别:")
print("1. 基础压缩 (保留大部分功能,适合网页使用)")
print("2. 中等压缩 (平衡文件大小和功能)")
print("3. 激进压缩 (最小文件大小,可能影响显示效果)")
while True:
choice = input("\n请输入选择 (1-3): ").strip()
if choice == "1":
compression_level = "basic"
break
elif choice == "2":
compression_level = "medium"
break
elif choice == "3":
compression_level = "aggressive"
break
else:
print("无效选择,请输入 1、2 或 3")
# 开始批量压缩
compress_fonts_batch(font_directory, compression_level=compression_level)
if __name__ == "__main__":
main()