最近几天发生了一些事情,导致《剑网3免费版》(台服)关服,于是昨天突发奇想,是不是想办法修改字体文件,就可以在简体客户端里面显示出繁体呢。

思绪

得到这个想法后,我便顺手拿出一个字体文件,阅读起了TTF文件的数据结构,随手谷歌了一篇文章,大致了解到TTF文件由矢量图形表(Glyphs)、字符到矢量图的映射表 (cmap)、矩阵样式等信息表 (hmtx、hdmx、OS/2 等)构成,与想象中设计基本一致。那么我们要做的事情,就是通过一份繁简转换表,从一个繁体字体文件中读取每一个繁体字符的矢量图、矩阵样式信息、字符映射表,并修改其映射关系到对应的简体字符,然后替换或者追加到简体字体文件对应的表中即可。

实现

翻阅一些现有库,发现有python项目 FontTools 可以快速将TTF字体转化为XML信息表,这样一来就不用花时间研究如何读写二进制的字体文件了,可以将时间集中在分析了XML文件中字体各个表的数据结构。经过一上午的数据结构分析,思路已然理清,通过调用ttx命令将源字体文件转化为XML文件,然后使用xml.etree.ElementTree读取转化后的文件,分别处理glyfcmaphmtxvmtxGlyphOrder表即可,其中glyfGlyphOrder表长度必须严格相等。

经过一小时的编码,逻辑已经实现,结果在运行时报了错

ERROR: Unhandled exception has occurred
Traceback (most recent call last):
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\tables\_c_m_a_p.py", line 114, in compile
    offset = seen[id(table.cmap)]
KeyError: 2602400102464

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttx.py", line 400, in main
    process(jobs, options)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttx.py", line 374, in process
    action(input, output, options)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\misc\loggingTools.py", line 367, in wrapper
    return func(*args, **kwds)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttx.py", line 292, in ttCompile
    ttf.save(output)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\ttFont.py", line 172, in save
    writer_reordersTables = self._save(tmp)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\ttFont.py", line 211, in _save
    self._writeTable(tag, writer, done, tableCache)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\ttFont.py", line 632, in _writeTable
    tabledata = self.getTableData(tag)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\ttFont.py", line 650, in getTableData
    return self.tables[tag].compile(self)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\tables\_c_m_a_p.py", line 116, in compile
    chunk = table.compile(ttFont)
  File "c:\users\root\appdata\local\programs\python\python38\lib\site-packages\fontTools\ttLib\tables\_c_m_a_p.py", line 838, in compile
    header = struct.pack(cmap_format_4_format, self.format, length, self.language,
struct.error: 'H' format requires 0 <= number <= 65535

追源码看了一下,是cmap_format_4长度超过了限制,这个表使用了 USHORT 作为长度存储类型,所以最大值为 65535。回头看了下字体文件,发现其中有大量的字符没有绘制,但是映射表中却填写了映射到一个空白矢量图上。于是我新加了一段处理逻辑,移除掉空白矢量图以及其映射关系,再次运行,这次没有报错得到了新的字体。

将字体放入游戏中,很完美,虽然编码实际上是GBK但是字体矢量图都被替换成了繁体字符,除了有些繁简对应一对多关系的可能有些许瑕疵,整体效果不错,优化代码后提交到 GitHub仓库,收工睡觉。(:3▓▒

TIM图片20200409003540.png