前言
- 平常字体包都有1M+的大小,所以网络请求耗时会比较长,所以对字体包的压缩也是前端优化的一个点。
- 但是前端如果想要特点字符打包成字体包,网上查阅资料后,都是把前端代码里面的字符获取,但是对于动态的内容,是没有进行处理的。
思路
获取到前端所有的字符,以及数据库所有的字符,对这些字符打包成字体包
每次上线前检查字符是否与之前的一致,不一致则重新打包
- 最开始的思路是通过
nodejs
的fontmin
依赖包去实现,但是该包对有的字体包没有作用(如:PingFangSC-Regular.ttf
字体),而有的也有作用(如:Alimama_ShuHeiTi.ttf
字体)。- 在使用
fontmin
的时候也要注意,如果使用require
引入,版本需要控制在v1内
- 在使用
- 后面选取了
python
技术栈fontTools
包- 没有对单个字体包考虑使用了什么字符,这样有点麻烦,所以在一样的字符集上处理。
环境
- 如果代码运行有问题,可以考虑是不是版本不匹配
- 版本
Python 3.9.6
fontTools 4.56.0
mysql.connector 9.2.0
- 依赖安装
pip install fontTools
pip install mysql-connector-python
python代码
import os
import re
import fontTools
from fontTools import subset
print("fontTools ", fontTools.__version__)
import mysql.connector
print("mysql.connector ", mysql.connector.__version__)
# 获取数据库字符集
def obtain_mysql_characters(config) -> set:
# 建立数据库连接
cnx = mysql.connector.connect(**config)
cursor = cnx.cursor()
# 获取所有表名
cursor.execute("SHOW TABLES")
tables = cursor.fetchall()
# 初始化字符集合
all_characters = set()
# 遍历每个表获取字符数据
for table in tables:
table_name = table[0]
cursor.execute(f"SELECT * FROM {table_name}")
rows = cursor.fetchall()
for row in rows:
for column in row:
if isinstance(column, str):
for char in column:
all_characters.add(char)
# 关闭数据库连接
cursor.close()
cnx.close()
print("数据库字符数量 ", len(all_characters))
return all_characters
# 获取前端项目字符集合
def obtain_font_characters(font_path_dir) -> set:
# 初始化字符集合
all_characters = set()
# 遍历前端项目文件
for root, dirs, files in os.walk(font_path_dir):
for file in files:
# 处理Vue文件,使用者可根据实际情况修改
if file.endswith(('.vue')):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 过滤注释
content = re.sub(r'//.*?\n|/\*.*?\*/', '', content, flags=re.DOTALL)
for char in content:
all_characters.add(char)
except Exception as e:
print(f'读取文件 {file_path} 时出错: {e}')
print("前端项目字符数量 ", len(all_characters))
return all_characters
# 对单个字体包进行子字符集提取
def obtain_font_characters_single(font_path, text, store_dir) -> set:
# 通过font_path获取字体名称
font_name = os.path.basename(font_path)
# 嵌入代码中,实现生成字体子集
options = subset.Options()
# 加载字体
font = subset.load_font(font_path, options)
subsetter = subset.Subsetter(options=options)
subsetter.populate(text=str(text))
subsetter.subset(font)
# 保存字体子集,存放在store_dir下,字体名称为font_name
subset.save_font(font, os.path.join(store_dir, font_name), options)
# 获取目录下,所有的ttf字体文件
def obtain_font_files(font_path_dir) -> list:
font_files = []
for root, dirs, files in os.walk(font_path_dir):
for file in files:
if file.endswith(('.ttf')):
font_files.append(os.path.join(root, file))
return font_files
# 主函数
if __name__ == "__main__":
# 数据库配置
mysqlConfig = {
'user': 'user',
'password': 'password',
'host': 'localhost',
'database': 'database',
'port': '3306'
}
# 根据实际情况修改为前端项目的根目录,使用正斜杠或者双斜杠避免转义问题 e.g. E:\\xx\\xx\\src
vue_project_dir = 'D:\\xxx\\src'
# 字体文件目录
font_dir = 'D:\\xxx\\src\\assets\\fonts-original'
# 字体文件存放目录
store_dir = 'D:\\xxx\\src\\assets\\fonts'
# 获取数据库字符集
mysql_characters = obtain_mysql_characters(mysqlConfig)
# 获取前端项目字符集合
font_characters = obtain_font_characters(vue_project_dir)
# 计算并集
intersection = mysql_characters | font_characters
# 输出结果
print("交集字符数量 ", len(intersection))
# 字体文件列表
font_files = obtain_font_files(font_dir)
# 输出字体文件列表
print("字体文件列表 ", font_files)
# 对每个字体文件进行子字符集提取
for font_file in font_files:
obtain_font_characters_single(font_file, intersection, store_dir)
效果
- 目录下面7个字体包
出现的问题
meta NOT subset; don’t know how to subset; dropped
- AI的解释(字体包亲测使用没有异常):在字体子集化过程中,meta表无法被处理,因此被丢弃。这通常不会影响字体的核心功能,但可能会导致某些元数据丢失。建议检查pyftsubset工具的版本和字体文件的完整性。
参考资料
- github仓库fonttools
- fonttools官方文档