✨ Added Monocraft font
This commit is contained in:
@@ -1,254 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Black.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Black.otf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Black.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Black.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Bold.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Bold.otf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Bold.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Bold.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-ExtraLight.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-ExtraLight.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Italic.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Italic.otf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Italic.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Italic.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Light.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Light.otf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Light.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-Light.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-SemiBold.otf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-SemiBold.otf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-SemiBold.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/otf/Monocraft-SemiBold.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Black.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Black.ttf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Black.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Black.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Bold.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Bold.ttf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Bold.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Bold.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-ExtraLight.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-ExtraLight.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Italic.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Italic.ttf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Italic.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Italic.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Light.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Light.ttf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Light.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-Light.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-SemiBold.ttf
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-SemiBold.ttf
Normal file
Binary file not shown.
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-SemiBold.woff2
Normal file
BIN
frontend/src/assets/fonts/Monocraft/ttf/Monocraft-SemiBold.woff2
Normal file
Binary file not shown.
179
frontend/src/assets/fonts/README.md
Normal file
179
frontend/src/assets/fonts/README.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# 字体压缩工具使用指南
|
||||||
|
|
||||||
|
## 📖 简介
|
||||||
|
|
||||||
|
`font_compressor.py` 是一个通用的字体压缩工具,可以:
|
||||||
|
- ✅ 将 TTF、OTF、WOFF 字体文件转换为 WOFF2 格式
|
||||||
|
- ✅ 支持相对路径和绝对路径
|
||||||
|
- ✅ 自动生成 CSS 字体定义文件
|
||||||
|
- ✅ 智能识别字体字重和样式
|
||||||
|
- ✅ 批量处理整个目录(包括子目录)
|
||||||
|
|
||||||
|
## 🚀 前置要求
|
||||||
|
|
||||||
|
安装 Python 依赖包:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install fonttools brotli
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 使用方法
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入 fonts 目录
|
||||||
|
cd frontend/src/assets/fonts
|
||||||
|
|
||||||
|
# 交互式模式处理当前目录
|
||||||
|
python font_compressor.py
|
||||||
|
|
||||||
|
# 处理相对路径的 Monocraft 目录
|
||||||
|
python font_compressor.py Monocraft
|
||||||
|
|
||||||
|
# 处理相对路径并指定压缩级别
|
||||||
|
python font_compressor.py Monocraft -l basic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生成 CSS 文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 压缩 Monocraft 字体并生成 CSS 文件
|
||||||
|
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
|
||||||
|
|
||||||
|
# 压缩 Hack 字体并生成 CSS
|
||||||
|
python font_compressor.py Hack -l basic -c ../styles/hack_fonts_new.css
|
||||||
|
|
||||||
|
# 压缩 OpenSans 字体并生成 CSS
|
||||||
|
python font_compressor.py OpenSans -l medium -c ../styles/opensans_fonts.css
|
||||||
|
```
|
||||||
|
|
||||||
|
### 高级用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用绝对路径
|
||||||
|
python font_compressor.py E:\Go_WorkSpace\voidraft\frontend\src\assets\fonts\Monocraft -l basic -c monocraft.css
|
||||||
|
|
||||||
|
# 不同压缩级别
|
||||||
|
python font_compressor.py Monocraft -l basic # 基础压缩,保留所有功能
|
||||||
|
python font_compressor.py Monocraft -l medium # 中等压缩,平衡大小和功能
|
||||||
|
python font_compressor.py Monocraft -l aggressive # 激进压缩,最小文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ 命令行参数
|
||||||
|
|
||||||
|
| 参数 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| `directory` | 字体目录(相对/绝对路径) | `Monocraft` 或 `/path/to/fonts` |
|
||||||
|
| `-l, --level` | 压缩级别 (basic/medium/aggressive) | `-l basic` |
|
||||||
|
| `-c, --css` | CSS 输出文件路径 | `-c monocraft.css` |
|
||||||
|
| `--version` | 显示版本信息 | `--version` |
|
||||||
|
| `-h, --help` | 显示帮助信息 | `-h` |
|
||||||
|
|
||||||
|
## 📊 压缩级别说明
|
||||||
|
|
||||||
|
### basic(基础) - 推荐
|
||||||
|
- 保留大部分字体功能
|
||||||
|
- 适合网页使用
|
||||||
|
- 压缩率约 30-40%
|
||||||
|
|
||||||
|
### medium(中等)
|
||||||
|
- 移除一些不常用的功能
|
||||||
|
- 平衡文件大小和功能
|
||||||
|
- 压缩率约 40-50%
|
||||||
|
|
||||||
|
### aggressive(激进)
|
||||||
|
- 最大程度减小文件大小
|
||||||
|
- 可能影响高级排版功能
|
||||||
|
- 压缩率约 50-60%
|
||||||
|
|
||||||
|
## 📁 输出结果
|
||||||
|
|
||||||
|
### 字体文件
|
||||||
|
压缩后的 `.woff2` 文件会保存在原文件相同的目录下,例如:
|
||||||
|
- `Monocraft/ttf/Monocraft-Bold.ttf` → `Monocraft/ttf/Monocraft-Bold.woff2`
|
||||||
|
- `Hack/hack-regular.ttf` → `Hack/hack-regular.woff2`
|
||||||
|
|
||||||
|
### CSS 文件
|
||||||
|
生成的 CSS 文件会包含:
|
||||||
|
- 自动识别的字体家族名称
|
||||||
|
- 正确的字重和样式设置
|
||||||
|
- 使用相对路径的字体引用
|
||||||
|
- 按字重排序的 `@font-face` 定义
|
||||||
|
|
||||||
|
生成的 CSS 示例:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 自动生成的字体文件 */
|
||||||
|
/* 由 font_compressor.py 生成 */
|
||||||
|
|
||||||
|
/* Monocraft 字体家族 */
|
||||||
|
|
||||||
|
/* Monocraft Light */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Bold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 实际使用示例
|
||||||
|
|
||||||
|
### 示例 1: 压缩 Monocraft 字体
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend/src/assets/fonts
|
||||||
|
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
|
||||||
|
```
|
||||||
|
|
||||||
|
这将:
|
||||||
|
1. 扫描 `Monocraft/ttf` 和 `Monocraft/otf` 目录
|
||||||
|
2. 将所有字体文件转换为 WOFF2
|
||||||
|
3. 在 `frontend/src/assets/styles/monocraft_fonts.css` 生成 CSS 文件
|
||||||
|
|
||||||
|
### 示例 2: 批量处理多个字体目录
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend/src/assets/fonts
|
||||||
|
|
||||||
|
# 压缩 Monocraft
|
||||||
|
python font_compressor.py Monocraft -l basic -c ../styles/monocraft_fonts.css
|
||||||
|
|
||||||
|
# 压缩 OpenSans
|
||||||
|
python font_compressor.py OpenSans -l basic -c ../styles/opensans_fonts.css
|
||||||
|
|
||||||
|
# 压缩 Hack(已有 CSS,只需生成新版本对比)
|
||||||
|
python font_compressor.py Hack -l basic -c ../styles/hack_fonts_new.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 字体信息自动识别
|
||||||
|
|
||||||
|
工具会自动从文件名识别:
|
||||||
|
- **字重**:Thin(100), Light(300), Regular(400), Medium(500), SemiBold(600), Bold(700), Black(900)
|
||||||
|
- **样式**:normal, italic
|
||||||
|
- **字体家族**:自动去除字重和样式后缀
|
||||||
|
|
||||||
|
支持的命名格式:
|
||||||
|
- `FontName-Bold.ttf`
|
||||||
|
- `FontName_Bold.otf`
|
||||||
|
- `FontName-BoldItalic.ttf`
|
||||||
|
- `FontName_SemiBold_Italic.woff`
|
||||||
|
|
||||||
|
|
||||||
|
## 📞 获取帮助
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python font_compressor.py --help
|
||||||
|
```
|
||||||
|
|
||||||
494
frontend/src/assets/fonts/font_compressor.py
Normal file
494
frontend/src/assets/fonts/font_compressor.py
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
通用字体压缩工具
|
||||||
|
使用 fonttools 库将字体文件转换为 WOFF2 格式,减小文件大小
|
||||||
|
支持 TTF、OTF、WOFF 等格式的字体文件
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple, Dict, Optional
|
||||||
|
|
||||||
|
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, exclude_woff2: bool = False) -> List[str]:
|
||||||
|
"""查找目录中的所有字体文件"""
|
||||||
|
if exclude_woff2:
|
||||||
|
font_extensions = ['.ttf', '.otf', '.woff']
|
||||||
|
else:
|
||||||
|
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 parse_font_info(filename: str) -> Dict[str, any]:
|
||||||
|
"""
|
||||||
|
从字体文件名解析字体信息(字重、样式等)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: 字体文件名(不含路径)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
包含字体信息的字典
|
||||||
|
"""
|
||||||
|
# 移除扩展名
|
||||||
|
name_without_ext = os.path.splitext(filename)[0]
|
||||||
|
|
||||||
|
# 字重映射
|
||||||
|
weight_mapping = {
|
||||||
|
'thin': (100, 'Thin'),
|
||||||
|
'extralight': (200, 'ExtraLight'),
|
||||||
|
'light': (300, 'Light'),
|
||||||
|
'regular': (400, 'Regular'),
|
||||||
|
'normal': (400, 'Regular'),
|
||||||
|
'medium': (500, 'Medium'),
|
||||||
|
'semibold': (600, 'SemiBold'),
|
||||||
|
'bold': (700, 'Bold'),
|
||||||
|
'extrabold': (800, 'ExtraBold'),
|
||||||
|
'black': (900, 'Black'),
|
||||||
|
'heavy': (900, 'Heavy'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 默认值
|
||||||
|
font_weight = 400
|
||||||
|
font_style = 'normal'
|
||||||
|
weight_name = 'Regular'
|
||||||
|
|
||||||
|
# 检查是否为斜体
|
||||||
|
if re.search(r'italic', name_without_ext, re.IGNORECASE):
|
||||||
|
font_style = 'italic'
|
||||||
|
|
||||||
|
# 检查字重
|
||||||
|
name_lower = name_without_ext.lower()
|
||||||
|
for weight_key, (weight_value, weight_label) in weight_mapping.items():
|
||||||
|
if weight_key in name_lower:
|
||||||
|
font_weight = weight_value
|
||||||
|
weight_name = weight_label
|
||||||
|
break
|
||||||
|
|
||||||
|
# 提取字体家族名称(移除字重和样式后缀)
|
||||||
|
family_name = name_without_ext
|
||||||
|
for weight_key, (_, weight_label) in weight_mapping.items():
|
||||||
|
family_name = re.sub(r'[-_]?' + weight_label, '', family_name, flags=re.IGNORECASE)
|
||||||
|
family_name = re.sub(r'[-_]?italic', '', family_name, flags=re.IGNORECASE)
|
||||||
|
family_name = family_name.strip('-_')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'family': family_name,
|
||||||
|
'weight': font_weight,
|
||||||
|
'style': font_style,
|
||||||
|
'weight_name': weight_name,
|
||||||
|
'full_name': name_without_ext
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_css(font_files: List[str], output_css_path: str, css_base_path: str):
|
||||||
|
"""
|
||||||
|
生成CSS字体文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
font_files: 字体文件路径列表(woff2文件)
|
||||||
|
output_css_path: 输出CSS文件路径
|
||||||
|
css_base_path: CSS文件相对于字体文件的基础路径
|
||||||
|
"""
|
||||||
|
# 按字体家族分组
|
||||||
|
font_groups: Dict[str, List[Dict]] = {}
|
||||||
|
|
||||||
|
for font_file in font_files:
|
||||||
|
if not font_file.endswith('.woff2'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
filename = os.path.basename(font_file)
|
||||||
|
font_info = parse_font_info(filename)
|
||||||
|
|
||||||
|
# 计算相对路径
|
||||||
|
font_dir = os.path.dirname(font_file)
|
||||||
|
css_dir = os.path.dirname(output_css_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 计算从CSS文件到字体文件的相对路径
|
||||||
|
rel_path = os.path.relpath(font_file, css_dir)
|
||||||
|
# 统一使用正斜杠(适用于Web)
|
||||||
|
rel_path = rel_path.replace('\\', '/')
|
||||||
|
except ValueError:
|
||||||
|
# 如果在不同驱动器上,使用绝对路径
|
||||||
|
rel_path = font_file.replace('\\', '/')
|
||||||
|
|
||||||
|
font_info['path'] = rel_path
|
||||||
|
|
||||||
|
family = font_info['family']
|
||||||
|
if family not in font_groups:
|
||||||
|
font_groups[family] = []
|
||||||
|
font_groups[family].append(font_info)
|
||||||
|
|
||||||
|
# 生成CSS内容
|
||||||
|
css_lines = ['/* 自动生成的字体文件 */', '/* 由 font_compressor.py 生成 */', '']
|
||||||
|
|
||||||
|
for family, fonts in sorted(font_groups.items()):
|
||||||
|
css_lines.append(f'/* {family} 字体家族 */')
|
||||||
|
css_lines.append('')
|
||||||
|
|
||||||
|
# 按字重排序
|
||||||
|
fonts.sort(key=lambda x: (x['weight'], x['style']))
|
||||||
|
|
||||||
|
for font in fonts:
|
||||||
|
css_lines.append(f"/* {family} {font['weight_name']}{' Italic' if font['style'] == 'italic' else ''} */")
|
||||||
|
css_lines.append('@font-face {')
|
||||||
|
css_lines.append(f" font-family: '{family}';")
|
||||||
|
css_lines.append(f" src: url('{font['path']}') format('woff2');")
|
||||||
|
css_lines.append(f" font-weight: {font['weight']};")
|
||||||
|
css_lines.append(f" font-style: {font['style']};")
|
||||||
|
css_lines.append(' font-display: swap;')
|
||||||
|
css_lines.append('}')
|
||||||
|
css_lines.append('')
|
||||||
|
|
||||||
|
# 写入CSS文件
|
||||||
|
with open(output_css_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(css_lines))
|
||||||
|
|
||||||
|
print(f"[OK] CSS文件已生成: {output_css_path}")
|
||||||
|
print(f" 包含 {sum(len(fonts) for fonts in font_groups.values())} 个字体定义")
|
||||||
|
print(f" 字体家族: {', '.join(sorted(font_groups.keys()))}")
|
||||||
|
|
||||||
|
def compress_fonts_batch(font_directory: str, compression_level: str = "basic") -> List[str]:
|
||||||
|
"""
|
||||||
|
批量压缩字体文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
font_directory: 字体文件目录
|
||||||
|
compression_level: 压缩级别
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
生成的woff2文件路径列表
|
||||||
|
"""
|
||||||
|
if not os.path.exists(font_directory):
|
||||||
|
print(f"错误: 目录 {font_directory} 不存在")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 查找所有字体文件(排除已经是woff2的)
|
||||||
|
font_files = find_font_files(font_directory, exclude_woff2=True)
|
||||||
|
|
||||||
|
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
|
||||||
|
generated_woff2_files = []
|
||||||
|
|
||||||
|
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
|
||||||
|
generated_woff2_files.append(output_file)
|
||||||
|
|
||||||
|
# 计算压缩率
|
||||||
|
compression_ratio = (1 - compressed_size / original_size) * 100
|
||||||
|
|
||||||
|
print(f" [OK] 成功: {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)}")
|
||||||
|
|
||||||
|
return generated_woff2_files
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
# 解析命令行参数
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='通用字体压缩工具 - 将字体文件转换为 WOFF2 格式并生成CSS',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog='''
|
||||||
|
使用示例:
|
||||||
|
%(prog)s # 交互式模式,处理当前目录
|
||||||
|
%(prog)s Monocraft # 处理相对路径目录
|
||||||
|
%(prog)s Monocraft -l basic # 使用基础压缩级别
|
||||||
|
%(prog)s Monocraft -l basic -c monocraft.css # 压缩并生成CSS文件
|
||||||
|
%(prog)s /path/to/fonts -l medium -c fonts.css # 使用绝对路径
|
||||||
|
|
||||||
|
压缩级别说明:
|
||||||
|
basic - 基础压缩:保留大部分功能,适合网页使用
|
||||||
|
medium - 中等压缩:平衡文件大小和功能
|
||||||
|
aggressive - 激进压缩:最小文件大小,可能影响显示效果
|
||||||
|
|
||||||
|
CSS生成说明:
|
||||||
|
使用 -c/--css 选项生成CSS文件,自动使用相对路径引用字体文件
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'directory',
|
||||||
|
nargs='?',
|
||||||
|
default=None,
|
||||||
|
help='字体文件目录路径(支持相对/绝对路径,默认为当前脚本所在目录)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-l', '--level',
|
||||||
|
choices=['basic', 'medium', 'aggressive'],
|
||||||
|
default=None,
|
||||||
|
help='压缩级别:basic(基础)、medium(中等)、aggressive(激进)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--css',
|
||||||
|
default=None,
|
||||||
|
help='生成CSS文件路径(相对于脚本位置或绝对路径)'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--version',
|
||||||
|
action='version',
|
||||||
|
version='%(prog)s 2.0'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("通用字体压缩工具 v2.0")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 检查依赖
|
||||||
|
if not check_dependencies():
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取脚本所在目录
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# 确定字体目录
|
||||||
|
if args.directory:
|
||||||
|
# 支持相对路径和绝对路径
|
||||||
|
if os.path.isabs(args.directory):
|
||||||
|
font_directory = args.directory
|
||||||
|
else:
|
||||||
|
font_directory = os.path.join(script_dir, args.directory)
|
||||||
|
font_directory = os.path.abspath(font_directory)
|
||||||
|
else:
|
||||||
|
# 默认使用当前脚本所在目录
|
||||||
|
font_directory = script_dir
|
||||||
|
|
||||||
|
# 检查目录是否存在
|
||||||
|
if not os.path.exists(font_directory):
|
||||||
|
print(f"\n错误: 目录不存在: {font_directory}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"\n字体目录: {font_directory}")
|
||||||
|
|
||||||
|
# 确定压缩级别
|
||||||
|
compression_level = args.level
|
||||||
|
|
||||||
|
if compression_level is None:
|
||||||
|
# 交互式选择压缩级别
|
||||||
|
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")
|
||||||
|
|
||||||
|
# 开始批量压缩
|
||||||
|
print()
|
||||||
|
generated_files = compress_fonts_batch(font_directory, compression_level=compression_level)
|
||||||
|
|
||||||
|
# 生成CSS文件
|
||||||
|
if args.css and generated_files:
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("生成CSS文件...")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# 确定CSS输出路径
|
||||||
|
if os.path.isabs(args.css):
|
||||||
|
css_path = args.css
|
||||||
|
else:
|
||||||
|
css_path = os.path.join(script_dir, args.css)
|
||||||
|
css_path = os.path.abspath(css_path)
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
css_dir = os.path.dirname(css_path)
|
||||||
|
if css_dir and not os.path.exists(css_dir):
|
||||||
|
os.makedirs(css_dir)
|
||||||
|
|
||||||
|
# 生成CSS
|
||||||
|
generate_css(generated_files, css_path, script_dir)
|
||||||
|
elif args.css and not generated_files:
|
||||||
|
print("\n警告: 没有成功生成WOFF2文件,跳过CSS生成")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("全部完成!")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/* 导入所有CSS文件 */
|
/* 导入所有CSS文件 */
|
||||||
@import 'normalize.css';
|
@import 'normalize.css';
|
||||||
@import 'variables.css';
|
@import 'variables.css';
|
||||||
@import "harmony_fonts.css";
|
|
||||||
@import 'scrollbar.css';
|
@import 'scrollbar.css';
|
||||||
|
@import "harmony_fonts.css";
|
||||||
@import 'hack_fonts.css';
|
@import 'hack_fonts.css';
|
||||||
@import 'opensans_fonts.css';
|
@import 'opensans_fonts.css';
|
||||||
|
@import "monocraft_fonts.css";
|
||||||
202
frontend/src/assets/styles/monocraft_fonts.css
Normal file
202
frontend/src/assets/styles/monocraft_fonts.css
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/* 自动生成的字体文件 */
|
||||||
|
/* 由 font_compressor.py 生成 */
|
||||||
|
|
||||||
|
/* Monocraft 字体家族 */
|
||||||
|
|
||||||
|
/* Monocraft ExtraLight Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-ExtraLight-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft ExtraLight Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-ExtraLight-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft ExtraLight */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-ExtraLight.woff2') format('woff2');
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft ExtraLight */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-ExtraLight.woff2') format('woff2');
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Light Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Light-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Light Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Light-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Light */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Light */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Regular Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Regular Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft SemiBold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-SemiBold-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft SemiBold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-SemiBold-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft SemiBold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-SemiBold.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft SemiBold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-SemiBold.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Bold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Bold-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Bold Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Bold-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Bold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Bold */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Black Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Black-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Black Italic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Black-Italic.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Black */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/otf/Monocraft-Black.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monocraft Black */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('../fonts/Monocraft/ttf/Monocraft-Black.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
@@ -13,6 +13,10 @@ export const FONT_OPTIONS = [
|
|||||||
label: 'Open Sans',
|
label: 'Open Sans',
|
||||||
value: '"Open Sans"'
|
value: '"Open Sans"'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Monocraft',
|
||||||
|
value: 'Monocraft'
|
||||||
|
},
|
||||||
// Common system fonts
|
// Common system fonts
|
||||||
{
|
{
|
||||||
label: 'Arial',
|
label: 'Arial',
|
||||||
@@ -46,7 +50,7 @@ export const FONT_OPTIONS = [
|
|||||||
label: 'System UI',
|
label: 'System UI',
|
||||||
value: 'system-ui'
|
value: 'system-ui'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Chinese fonts
|
// Chinese fonts
|
||||||
{
|
{
|
||||||
label: 'Microsoft YaHei',
|
label: 'Microsoft YaHei',
|
||||||
@@ -56,7 +60,7 @@ export const FONT_OPTIONS = [
|
|||||||
label: 'PingFang SC',
|
label: 'PingFang SC',
|
||||||
value: '"PingFang SC"'
|
value: '"PingFang SC"'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Popular programming fonts
|
// Popular programming fonts
|
||||||
{
|
{
|
||||||
label: 'JetBrains Mono',
|
label: 'JetBrains Mono',
|
||||||
|
|||||||
Reference in New Issue
Block a user