解析 TrueType/OpenType 格式的可变字体(Variable Font),提取其所有命名实例(Named Instances) 的名称信息
可变字体(Variable Font)命名实例解析工具 —— 基于 FreeType2 的 C++ 实现
文章目录
- **可变字体(Variable Font)命名实例解析工具 —— 基于 FreeType2 的 C++ 实现**
- **代码大纲**
- **一、项目概述**
- **二、技术栈与依赖**
- **三、关键功能模块**
- **1. 编码转换工具函数**
- **2. SFNT 名称表解码函数**
- **3. 可变字体命名实例提取**
- **4. 主函数流程(main)**
- **四、输出示例**
- **五、潜在改进方向**
- **六、编译与运行要求**
- **七、总结**
代码大纲
一、项目概述
本程序是一个基于 FreeType2 库的 C++ 工具,用于解析 TrueType/OpenType 格式的可变字体(Variable Font),提取其所有命名实例(Named Instances) 的名称信息与设计坐标。通过读取字体的 SFNT 名称表(Name Table)并进行编码转换,实现跨平台字体名称的正确解码,最终输出每个实例的多种名称及其对应的轴坐标(如字重、字宽等)。
二、技术栈与依赖
- 核心库:FreeType2(
freetype.h
及相关模块) - 编码处理:UTF-16BE → UTF-16 → UTF-8(使用 Windows API)
- 图像支持(预留):JPEG 库(
jpeglib.h
,当前未使用) - 平台:Windows(依赖
windows.h
实现编码转换) - 语言标准:C++11 或更高(使用
std::map
、std::set
、范围 for 循环等)
三、关键功能模块
1. 编码转换工具函数
-
utf16_to_utf8(const wchar_t*, size_t)
使用WideCharToMultiByte
将 UTF-16 宽字符串转换为 UTF-8 编码的std::string
。 -
utf16be_to_utf16(const uint8_t*, size_t)
将大端序(Big-Endian)的 UTF-16 字节流转换为本地字节序的std::wstring
,用于处理 SFNT 表中的 Unicode 字符串。
2. SFNT 名称表解码函数
decode_sfnt_name(const FT_SfntName&)
根据platform_id
和encoding_id
对字体名称进行智能解码:- Macintosh 平台:支持 Roman 编码
- Microsoft 平台:支持 Unicode 和 Symbol 编码(UTF-16BE)
- Apple Unicode 平台:直接按 UTF-16BE 解析
- ISO 平台:原始字节转字符串
- 默认回退机制确保兼容性
3. 可变字体命名实例提取
get_variable_font_instances(FT_Face)
核心函数,执行以下操作:- 验证字体是否为多主/可变字体(
FT_HAS_MULTIPLE_MASTERS
) - 获取
FT_MM_Var
结构,包含轴(axis)与命名实例(named styles) - 遍历每个命名实例:
- 通过
strid
从名称表获取主名称 - 遍历所有名称条目,收集家族名、子家族名、全名、PostScript 名等
- 若无有效名称,则生成默认名称(如 “实例_0”)
- 通过
- 返回
map<int, set<string>>
:实例索引 → 名称集合
- 验证字体是否为多主/可变字体(
4. 主函数流程(main)
- 初始化 FreeType 库
- 加载指定路径的可变字体文件(
.ttf
或.otf
) - 获取并验证可变字体信息(
FT_MM_Var
) - 调用
get_variable_font_instances
提取实例信息 - 输出结果:
- 实例索引
- 所有解析出的名称
- 每个实例在各设计轴上的坐标值(归一化为浮点数)
- 释放资源并退出
四、输出示例
找到 5 个命名实例:实例 0:- Thin- Regular- Noto Sans SC Thin设计坐标: Weight=100.0 Wdth=100.0 实例 1:- ExtraLight- Noto Sans SC ExtraLight设计坐标: Weight=200.0 Wdth=100.0 ...
五、潜在改进方向
- 跨平台编码转换:当前依赖 Windows API,可替换为
iconv
或std::wstring_convert
实现跨平台支持。 - 错误处理增强:增加文件存在性检查、权限验证等。
- 输出格式扩展:支持 JSON 或 CSV 输出,便于集成到其他系统。
- GUI 支持:结合图形库展示字体预览。
- 动态轴信息输出:打印轴的最小/最大/默认值。
六、编译与运行要求
- 安装 FreeType2 开发库(建议版本 2.10+)
- 链接 FreeType2 库(如
freetyped.lib
或libfreetype.a
) - 使用支持 C++11 的编译器(如 MSVC、g++、clang++)
- 示例编译命令(MSVC):
cl main.cpp /I"path/to/freetype/include" /link "path/to/freetype/lib/freetyped.lib"
七、总结
本程序展示了如何利用 FreeType2 深度解析现代可变字体的元数据,尤其在处理多语言字体名称时,正确实现 UTF-16 编码转换至关重要。该工具可用于字体开发、调试、自动化测试或字体信息提取系统中。
// FreeType2基础头文件
#include <ft2build.h>
// FreeType多主字体(可变字体)支持头文件
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H// C++标准容器和字符串处理
#include <map> // 映射容器
#include <string> // 字符串类
#include <vector> // 动态数组
#include <iostream> // 输入输出流
#include <set>
// FreeType SFNT名称表相关头文件(处理字体命名信息)
#include FT_SFNT_NAMES_H
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H// FreeType TrueType/OpenType表相关头文件
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TAGS_H // TrueType标签定义
#include FT_SFNT_NAMES_H // SFNT名称表
#include FT_TYPE1_TABLES_H // Type1字体表
#include FT_MULTIPLE_MASTERS_H // 多主字体支持
#include FT_TRUETYPE_IDS_H // TrueType标识符// JPEG图像处理库
#include <jpeglib.h>// Windows API头文件
#include <windows.h>/*** UTF-16转UTF-8编码转换函数(使用Windows API实现)* @param utf16_str UTF-16编码的宽字符指针* @param length 输入字符串长度* @return 转换后的UTF-8字符串*/
std::string utf16_to_utf8(const wchar_t* utf16_str, size_t length) {// 空指针或零长度检查if (!utf16_str || length == 0) return "";// 计算需要的UTF-8缓冲区大小int utf8_len = WideCharToMultiByte(CP_UTF8, 0, utf16_str,static_cast<int>(length),nullptr, 0, nullptr, nullptr);if (utf8_len <= 0) return "";// 分配缓冲区并执行转换std::string utf8_str(utf8_len, 0);WideCharToMultiByte(CP_UTF8, 0, utf16_str, static_cast<int>(length),&utf8_str[0], utf8_len, nullptr, nullptr);// 移除转换后可能存在的空字符utf8_str.erase(std::remove(utf8_str.begin(), utf8_str.end(), '\0'), utf8_str.end());return utf8_str;
}/*** 将UTF-16BE(大端序)转换为本地UTF-16格式* @param src UTF-16BE字节数组指针* @param len 输入数据长度* @return 转换后的宽字符串*/
std::wstring utf16be_to_utf16(const uint8_t* src, size_t len) {// 检查长度是否为2的倍数(UTF-16是2字节编码)if (len % 2 != 0) return L"";std::wstring result;result.reserve(len / 2); // 预分配空间// 每2字节处理一个字符(大端序转小端序)for (size_t i = 0; i < len; i += 2) {wchar_t c = (src[i] << 8) | src[i + 1]; // 大端序转换result.push_back(c);}return result;
}/*** 解码SFNT名称表中的字体名称* @param name SFNT名称表条目引用* @return 解码后的UTF-8字符串*/
std::string decode_sfnt_name(const FT_SfntName& name) {// 空字符串处理if (name.string_len == 0) return "";// 1. 处理Mac平台编码if (name.platform_id == TT_PLATFORM_MACINTOSH) {if (name.encoding_id == TT_MAC_ID_ROMAN) {std::string result(reinterpret_cast<const char*>(name.string), name.string_len);result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());return result;}}// 2. 处理Windows平台Unicode编码else if (name.platform_id == TT_PLATFORM_MICROSOFT) {switch (name.encoding_id) {case TT_MS_ID_UNICODE_CS:case TT_MS_ID_SYMBOL_CS: {std::wstring utf16_str = utf16be_to_utf16(name.string, name.string_len);return utf16_to_utf8(utf16_str.c_str(), utf16_str.length());}}}// 3. 处理Apple Unicode平台else if (name.platform_id == TT_PLATFORM_APPLE_UNICODE) {std::wstring utf16_str = utf16be_to_utf16(name.string, name.string_len);return utf16_to_utf8(utf16_str.c_str(), utf16_str.length());}// 4. 处理ISO平台编码else if (name.platform_id == TT_PLATFORM_ISO) {std::string result(reinterpret_cast<const char*>(name.string), name.string_len);result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());return result;}// 默认处理方式std::string result(reinterpret_cast<const char*>(name.string), name.string_len);result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());return result;
}/*** 获取可变字体的命名实例映射* @param face FreeType字体face对象* @return 映射表:实例索引 -> 名称集合*/
std::map<int, std::set<std::string>> get_variable_font_instances(FT_Face face) {std::map<int, std::set<std::string>> instance_map;// 检查字体是否有效if (!face) {std::cerr << "错误:字体未加载" << std::endl;return instance_map;}// 检查是否是多主字体(可变字体)if (!FT_HAS_MULTIPLE_MASTERS(face)) {std::cerr << "错误:不是可变字体" << std::endl;return instance_map;}// 获取可变字体信息FT_MM_Var* mm_var = nullptr;if (FT_Get_MM_Var(face, &mm_var) != 0) {std::cerr << "错误:无法获取可变字体信息" << std::endl;return instance_map;}// 遍历所有命名实例for (FT_UInt inst_idx = 0; inst_idx < mm_var->num_namedstyles; ++inst_idx) {FT_Var_Named_Style& instance = mm_var->namedstyle[inst_idx];std::set<std::string> names;// 获取实例的主要名称(从name表)FT_SfntName name;if (FT_Get_Sfnt_Name(face, instance.strid, &name) == 0) {std::string decoded_name = decode_sfnt_name(name);if (!decoded_name.empty()) {names.insert(decoded_name);}}// 获取其他可能的名称(遍历所有name表条目)FT_UInt name_count = FT_Get_Sfnt_Name_Count(face);for (FT_UInt i = 0; i < name_count; ++i) {FT_SfntName name_entry;if (FT_Get_Sfnt_Name(face, i, &name_entry) == 0) {// 检查是否是当前实例的其他名称if (name_entry.name_id == TT_NAME_ID_FONT_FAMILY ||name_entry.name_id == TT_NAME_ID_FONT_SUBFAMILY||name_entry.name_id == TT_NAME_ID_FULL_NAME ||name_entry.name_id == TT_NAME_ID_PS_NAME) {std::string decoded_name = decode_sfnt_name(name_entry);if (!decoded_name.empty()) {names.insert(decoded_name);}}}}// 如果没有找到名称,使用默认名称if (names.empty()) {names.insert("实例_" + std::to_string(inst_idx));}instance_map[inst_idx] = names;}// 释放资源FT_Done_MM_Var(face->glyph->library, mm_var);return instance_map;
}// 主函数
int main() {FT_Library library;FT_Face face;// 初始化FreeType库if (FT_Init_FreeType(&library)) {std::cerr << "无法初始化FreeType库" << std::endl;return 1;}// 加载可变字体文件const char* font_path = "D:/NotoSansSC-VF_black.ttf"; // 替换为实际路径if (FT_New_Face(library, font_path, 0, &face)) {std::cerr << "无法加载字体文件: " << font_path << std::endl;FT_Done_FreeType(library);return 1;}// 获取可变字体信息FT_MM_Var* mm_var = nullptr;if (FT_Get_MM_Var(face, &mm_var) != 0) {std::cerr << "无法获取可变字体信息" << std::endl;FT_Done_Face(face);FT_Done_FreeType(library);return 1;}// 获取字体实例映射auto instances = get_variable_font_instances(face);// 打印结果std::cout << "找到 " << instances.size() << " 个命名实例:\n";for (const auto& [index, names] : instances) {std::cout << " 实例 " << index << ":\n";for (const auto& name : names) {std::cout << " - " << name << "\n";}// 打印实例的设计坐标if (index < mm_var->num_namedstyles) {std::cout << " 设计坐标: ";for (FT_UInt j = 0; j < mm_var->num_axis; j++) {std::cout << mm_var->axis[j].name << "="<< mm_var->namedstyle[index].coords[j] / 65536.0f << " ";}std::cout << "\n";}}// 释放资源FT_Done_MM_Var(face->glyph->library, mm_var);FT_Done_Face(face);FT_Done_FreeType(library);return 0;
}