当前位置: 首页 > news >正文

解析 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::mapstd::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_idencoding_id 对字体名称进行智能解码:
    • Macintosh 平台:支持 Roman 编码
    • Microsoft 平台:支持 Unicode 和 Symbol 编码(UTF-16BE)
    • Apple Unicode 平台:直接按 UTF-16BE 解析
    • ISO 平台:原始字节转字符串
    • 默认回退机制确保兼容性
3. 可变字体命名实例提取
  • get_variable_font_instances(FT_Face)
    核心函数,执行以下操作:
    1. 验证字体是否为多主/可变字体(FT_HAS_MULTIPLE_MASTERS
    2. 获取 FT_MM_Var 结构,包含轴(axis)与命名实例(named styles)
    3. 遍历每个命名实例:
      • 通过 strid 从名称表获取主名称
      • 遍历所有名称条目,收集家族名、子家族名、全名、PostScript 名等
      • 若无有效名称,则生成默认名称(如 “实例_0”)
    4. 返回 map<int, set<string>>:实例索引 → 名称集合
4. 主函数流程(main)
  1. 初始化 FreeType 库
  2. 加载指定路径的可变字体文件(.ttf.otf
  3. 获取并验证可变字体信息(FT_MM_Var
  4. 调用 get_variable_font_instances 提取实例信息
  5. 输出结果:
    • 实例索引
    • 所有解析出的名称
    • 每个实例在各设计轴上的坐标值(归一化为浮点数)
  6. 释放资源并退出

四、输出示例

找到 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 ...

五、潜在改进方向

  1. 跨平台编码转换:当前依赖 Windows API,可替换为 iconvstd::wstring_convert 实现跨平台支持。
  2. 错误处理增强:增加文件存在性检查、权限验证等。
  3. 输出格式扩展:支持 JSON 或 CSV 输出,便于集成到其他系统。
  4. GUI 支持:结合图形库展示字体预览。
  5. 动态轴信息输出:打印轴的最小/最大/默认值。

六、编译与运行要求

  • 安装 FreeType2 开发库(建议版本 2.10+)
  • 链接 FreeType2 库(如 freetyped.liblibfreetype.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;
}
http://www.dtcms.com/a/331693.html

相关文章:

  • ESP32S3的LVGL配置参数解释、动画播放优化(更新中)
  • 4.1vue3的setup()
  • 《WebGL中FBO的底层运行逻辑》
  • 编程与数学 02-017 Python 面向对象编程 01课题、面向对象
  • 【会员专享数据】2000-2024年我国乡镇的逐日PM₁₀数据(Shp/Excel格式)
  • linux初始化配置
  • 计算机网络知识
  • 基于Java飞算AI的Spring Boot聊天室系统全流程实战
  • 【奔跑吧!Linux 内核(第二版)】第6章:简单的字符设备驱动(三)
  • CMake include_directories()使用指南
  • 从零开始的云计算生活——第四十三天,激流勇进,kubernetes模块之Pod资源对象
  • 莫队 Little Elephant and Array
  • GUI Grounding: ScreenSpot
  • 力扣-62.不同路径
  • AM原理与配置
  • 【网络安全测试】手机APP安全测试工具NowSecure 使用指导手册(有关必回)
  • Monsters
  • Redis7学习--持久化机制 RDB与AOF
  • 【SLAM】不同相机模型及其常见的链式求导推导
  • 从“静态文档”到“交互式模拟”:Adobe Captivate企业培训解决方案深度实践
  • OpenCV 高斯模糊降噪
  • IDEA如何引用brew安装的openjdk
  • ts概念讲解
  • 重塑隐私边界,微算法科技(NASDAQ:MLGO)开发基于边缘计算的轻量级区块链身份隐私保护方案
  • QT - QT开发进阶合集
  • 0814 TCP和DUP通信协议
  • 【DFS系列 | 暴力搜索与回溯剪枝】DFS问题实战:如何通过剪枝优化暴力搜索效率
  • Java Map集合精讲:键值对高效操作指南
  • (LeetCode 每日一题) 1780. 判断一个数字是否可以表示成三的幂的和 (数学、三进制数)
  • 【lucene】DocumentsWriterFlushControl