如何用Node.js/Grunt拆分woff2字体为多字符集文件并识别Unicode范围
我太懂你这种糟心的情况了——之前那些工具要么只会粗暴按大小拆分,要么字符集识别得稀里糊涂,完全没法精准拆分出拉丁、西里尔和中文字符对应的独立字体文件。下面给你一套基于Node.js的可行方案,还能无缝整合到Grunt工作流里:
核心思路:精准匹配字符集的Unicode范围
要实现精准拆分,核心是先明确目标字符集的Unicode范围(或自动识别字符所属脚本),再过滤字体中对应字符的字形,最后生成新的字体文件。两种思路都可行,下面分别说明:
方案1:硬编码Unicode范围(稳定可靠)
如果你清楚目标字符集的标准Unicode范围,可以直接硬编码过滤,这种方式最稳定,不会出现识别错误。我们用Node.js的opentype.js库来解析和生成字体:
步骤1:安装依赖
npm install opentype.js woff2 --save-dev
(woff2用于把生成的TTF转成WOFF2,避免opentype.js直接输出WOFF2可能出现的兼容问题)
步骤2:编写拆分脚本(split-font.js)
const opentype = require('opentype.js'); const fs = require('fs'); const woff2 = require('woff2'); // 预定义各字符集的Unicode范围(可根据需求调整) const characterSets = { latin: [ [0x0020, 0x007E], // 基本拉丁(含标点、数字) [0x00A0, 0x00FF], // 拉丁补充-1(带重音的拉丁字符) [0x0100, 0x017F], // 拉丁扩展-A [0x0180, 0x024F], // 拉丁扩展-B ], cyrillic: [ [0x0400, 0x04FF], // 西里尔基本字符 [0x0500, 0x052F], // 西里尔补充 [0x2DE0, 0x2DFF], // 西里尔扩展-A [0xA640, 0xA69F], // 西里尔扩展-B ], chinese: [ [0x4E00, 0x9FFF], // 中日韩统一表意文字(基本区,覆盖绝大多数常用中文) [0x3400, 0x4DBF], // 扩展A区(生僻字) ] }; // 判断字符码点是否在目标范围内 function isInRange(codePoint, ranges) { return ranges.some(([start, end]) => codePoint >= start && codePoint <= end); } // 核心拆分函数 async function splitFont(inputPath) { try { // 加载源字体文件 const font = await opentype.load(inputPath); // 遍历每个字符集,生成对应子集字体 for (const [setName, ranges] of Object.entries(characterSets)) { // 过滤出属于当前字符集的字形(跳过无Unicode码点的字形,比如空格以外的控制字符) const filteredGlyphs = font.glyphs.filter(glyph => { if (glyph.unicode === undefined) return false; return isInRange(glyph.unicode, ranges); }); // 保留源字体的元数据,替换为过滤后的字形 const newFont = new opentype.Font({ familyName: font.familyName, styleName: font.styleName, unitsPerEm: font.unitsPerEm, ascender: font.ascender, descender: font.descender, glyphs: filteredGlyphs }); // 先生成TTF,再转成WOFF2 const ttfBuffer = newFont.toBuffer(); const woff2Buffer = woff2.compress(ttfBuffer); // 保存文件 const outputPath = `${inputPath.replace('.woff2', '')}-${setName}.woff2`; fs.writeFileSync(outputPath, woff2Buffer); console.log(`✅ 已生成子集字体:${outputPath}`); } } catch (err) { console.error('❌ 拆分失败:', err); throw err; } } // 执行拆分(替换成你的源字体路径) splitFont('./your-source-font.woff2');
步骤3:整合到Grunt工作流
如果要把这个脚本加入Grunt构建流程,在Gruntfile.js里添加自定义任务:
module.exports = function(grunt) { grunt.initConfig({ // 你的其他Grunt任务配置(比如sass、uglify等) }); // 注册自定义字体拆分任务 grunt.registerTask('split-font', 'Split font into latin, cyrillic, chinese subsets', function() { const done = this.async(); const splitFont = require('./split-font.js'); splitFont('./path/to/your-source-font.woff2') .then(() => done()) .catch(err => { grunt.log.error(err); done(false); }); }); // 把拆分任务加入构建流程 grunt.registerTask('build', ['clean', 'sass', 'split-font']); };
方案2:自动识别字符所属脚本(灵活智能)
如果不想手动维护Unicode范围,可以用unicode-properties库自动判断字符所属的脚本(比如Latin、Cyrillic、Han),实现更灵活的拆分:
步骤1:安装额外依赖
npm install unicode-properties --save-dev
步骤2:修改拆分脚本的过滤逻辑
把之前的isInRange函数替换成以下代码:
const unicode = require('unicode-properties'); // 获取字符所属的脚本 function getCharacterScript(codePoint) { return unicode.getScript(codePoint); } // 修改过滤逻辑 const filteredGlyphs = font.glyphs.filter(glyph => { if (glyph.unicode === undefined) return false; const script = getCharacterScript(glyph.unicode); // 匹配对应脚本:Latin(拉丁)、Cyrillic(西里尔)、Han(中文) return (setName === 'latin' && script === 'Latin') || (setName === 'cyrillic' && script === 'Cyrillic') || (setName === 'chinese' && script === 'Han'); });
注意事项
- 测试生成的子集字体:一定要验证目标字符是否能正常显示,避免遗漏复合字形(比如带重音的拉丁字符)。
- 处理生僻字:如果需要包含中文生僻字,可以扩展
chinese的Unicode范围,加入扩展B/C/D区。 - 二进制依赖问题:
woff2和unicode-properties需要编译二进制文件,如果安装失败,确保你的环境有Python 3.x和C++编译工具(比如Windows上的Visual Studio Build Tools,Mac上的Xcode Command Line Tools)。
内容的提问来源于stack exchange,提问作者Tom M
相关产品推荐
相关产品推荐

