如何修复 Kindle 自定义字体名称显示问号的问题

前些天小伙伴“预盐”留言提了一个与 Kindle 自定义字体功能相关的问题。他按照《如何使用 Kindle 的原生自定义字体功能》这篇文章提供的步骤,将字体文件放到 Kindle 根目录中的 fonts 文件夹后,在字体选择面板中选择字体时发现,本该显示中文字体名称的地方却出现了如下图所示的一串问号。

中文字体名称显示问号

▲ 中文字体名称显示问号

此问题的出现可能是字体文件制作不规范导致的,我们可以参考 OpenType 规范(下称“规范”)并利用合适的工具修复此问题。本文提供了一种解决方案,可有效地解决 OpenType 格式(文件扩展名为 .otf、.otc、.ttf 或 .ttc)字体名称在 Kindle 的字体选择面板中无法正确显示的问题。

一、解决思路

OpenType 格式字体文件包含一系列以表格形式呈现的数据,这些数据的类型有很多,具体可查看规范。本文只关注与字体名称(选择字体时看到的字样)相关的元数据信息,即字体的“name”表。

字体的“name”表可以将多种语言的字符串(文本)关联到字体上,比如版权声明、字体名称、字族名称、样式名称等,其目的是为了在不同语言环境的操作系统中显示这些信息的相应语言版本。

如果一个字体的名称没有正确显示就意味着字体文件中的“name”表有问题,为解决这个问题,我们可以先使用工具将其提取出来,然后按照规范进行修正,最后再把修正好的“name”表合并到字体文件中。

二、准备工具

字体文件(即扩展名为 .otf、.otc、.ttf 或 .ttc 的文件)是独立的二进制文件,我们无法直接对其进行编辑,为了能够对其进行我们所需要的修改,必须借助专门的工具或将其转换成人类可读的文本文件。

本文使用的工具是 fontTools,一款基于 Python 的字体处理程序库,其中包含一款名为 ttx 的命令行工具,它可以将字体文件转换成扩展名为 .ttx 的 XML 文本文件,方便我们修改字体的相关信息。

使用 fontTools 需要确保你的操作系统安装版本大于等于 3.6 的 Python。如果你的系统没有安装 Python 或者版本低于 3.6,请前往 Python 官网下载安装(macOS 系统推荐通过 Homebrew 安装)。

Python 环境准备好后,可在“终端”或“命令提示符”中输入以下命令安装 fontTools:

pip3 install fonttools

fontTools 安装完成后,可输入如下命令,如果能正常输出版本号就表示安装成功:

ttx --version

三、操作步骤

虽然 ttx 程序可以将整个字体文件转换成 .ttx 文件,也能将 .ttx 文件转换回字体文件,但是如果字体文件较大的话,转换的 .ttx 文件也会很大,这会降低编辑和转换的效率。因此,为了大幅提高效率,我们只需单独提取出字体文件的“name”表进行修改,再利用 ttx 的合并功能将修改后的表合并回字体文件。

为方便展示操作步骤,下面虚构了一个有名称显示问题的字体文件 SampleSong.ttf(在实际操作时将该文件名换成实际的字体文件名即可)。对于步骤中涉及到规范的地方,书伴会做必要的解释。

1、从字体文件中提取出“name”表

要将字体文件中的“name”表单独提取出来,可先切换到字体所在的目录,然后运行如下命令:

ttx -t name SampleSong.ttf

* 提示:如果你处理的是“字体集(Font Collections)”文件(扩展名为 .ttc 或 .otc),需要在命令中指定字体集中单个字体的编号,即在选项 -t 前添加选项 -y,并在该选项后指定编号。如 ttx -y 0 -t 'name' SampleFonts.ttc

默认情况下,提取的“name”表文件名与字体文件名相同,存放位置也与字体文件相同,只是扩展名变成了 .ttx。本例中得到的文件为 SampleSong.ttx,用代码编辑器打开该文件,会看到类似这样的内容:

<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.22">
    <name>
        <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
            示例宋体
        </namerecord>
        <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
            Regular
        </namerecord>
        <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
            示例宋体 Regular; Version 1.0; 2019-09-27
        </namerecord>
        <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
            示例宋体 Regular
        </namerecord>
        <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
            Version 1.0 September 27, 2019
        </namerecord>
        <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
            SampleSong-Regular
        </namerecord>
    </name>
</ttFont>

这是字体文件中所有的“名称记录(Name Records)”,它由多个 <namerecord> 元素组成,每个元素都含有 nameIDplatformIDplatEncIDlangID 属性(分别对应着规范中的 nameID、platformID、encodingID 和 languageID),这些属性定义了各元素在字体中的意义。这些属性的含义如下:

  • nameID:名称 ID。常用的是 26 个预定义 ID(从 0 到 25),每个 ID 对应一种特定含义。其中 1 和 2 分别对应着“字族名称(Font Family name)”和“子字族名称(Font Subfamily name)”。
  • platformID:平台 ID。0 对应 Unicode,1 对应 Macintosh 平台,3 对应 Windows 平台。接下来的两个属性 encodingID 和 languageID 的值均取决于此 ID。本例采用 Windows 平台。
  • platEncID:特定平台编码 ID。Windows 平台下常用的编码 ID 为 1(Unicode BMP)。
  • langID:语言 ID。Windows 平台下的语言 ID 的值以 16 进制表示,其中美式英文为 0x409,简体中文为 0x804。这个属性是解决问题的关键,它决定了让系统以何种语言解析名称记录元素的内容。

* 提示:“字族名称”也称“系列名称”,即选择字体时显示的名称;“子字族名称”也称“样式名称”,即字重、斜体等字体特性。

名称记录元素的先后顺序取决于这些属性,必须先按平台 ID 排序,再按特定平台编码 ID 排序,接着按语言 ID 排序,最后按名称 ID 排序。这种有序的排列也有助于我们快速辨别某组记录对应何种语言。

了解了这些技术细节之后,我们就能够看出示例“name”表存在的问题,含有简体中文字符串的名称记录,其语言 ID 属性值却是 0x409(即美式英文),显然这会让某些系统(如 Kindle)无法正确解析。

为解决这个问题,就需要修正字体的名称记录中错误的语言 ID,使其与字符串的实际语言相匹配。

2、修正“name”表中错误的语言 ID

修正语言 ID 有两种方法:一种是把所有含有简体中文字符串的名称记录的语言 ID 都更改成 0x804;另一种是在不修改原有名称记录的情况下新增针对简体中文的名称记录,在本例中,只需要添加一项名称 ID 为 1 的名称记录,并将其语言 ID 设为简体中文(如下所示),就足以解决字体名称显示问题了。

<?xml version="1.0" encoding="UTF-8"?>
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.22">
    <name>
        <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
            示例宋体
        </namerecord>
        <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
            Regular
        </namerecord>
        <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
            示例宋体 Regular; Version 1.0; 2019-09-27
        </namerecord>
        <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
            示例宋体 Regular
        </namerecord>
        <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
            Version 1.0 April 25, 2021
        </namerecord>
        <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
            SampleSong-Regular
        </namerecord>
        <namerecord nameID="1" platformID="3" platEncID="1" langID="0x804">
            示例宋体
        </namerecord>
    </name>
</ttFont>

书伴推荐第二种方法。如果你选用第一种方法可能会产生一些副作用。以名称 ID 为 6 的名称记录为例,规范对该记录的建议为:应包含 Macintosh 和 Windows 两个平台的名称记录,且都应将语言 ID 设置为英文。若不小心设为中文,在为 macOS 系统添加字体时,就会遇到字体验证提示表结构错误的情况。

3、将修正的“name”表合并到字体

将字体“name”表中的语言 ID 错误修正之后,即可运行如下命令将“name”表合并回字体文件:

ttx -m SampleSong.ttf SampleSong.ttx

* 提示:如果你要处理的是“字体集(Font Collections)”文件(扩展名为 .ttc 或 .otc),需要注意,由于 ttx 无法直接对字体集文件进行合并操作,因此你需要先使用工具套装 Adobe Font Development Kit for OpenType (AFDKO) 中的 otc2otf 程序将字体集中的单个字体文件全部提取出来,再用 ttx 进行处理,最后用工具套装里面的 otf2ot 程序将重新合并。

命令执行成功后,会生成一个新的字体文件,默认情况下,新字体文件名会在原有文件名的基础上添加一个序号 #1,如 SampleSong#1.ttf(你也可在选项 -m 前添加选项 -o,并在其后自定义文件名)。

* 提示:在 macOS 系统中,如果字体文件名带有 # 符号,即便字体结构没问题,添加到字体册时也会出现奇怪的“系统验证”错误,无法通过字体的验证。如果你处理的字体文件需要在 macOS 系统中使用,建议删掉字体文件名中的 # 符号。

得到新生成的字体文件后,将其拷贝到 Kindle 中即可查看修正效果,如下所示:

中文字体名称显示问号修复效果

▲ 中文字体名称显示问号修复效果

至此,自定义字体名称无法在 Kindle 的字体选择面板中正常显示的问题就解决了。文章看起来很长,主要是因为添加了一些规范的描述,简单的说就是先利用 fontTools 里的 ttx 工具从字体文件中提取“name”表,然后并按照规范修正错误,最后将修正的“name”重新合并到字体文件中得到新的字体文件。

有帮助,分享给其他小伙伴:

发表评论

标注为 * 的是必填项。您填写的邮箱地址将会被保密。如果是在本站首次留言,审核后才能显示。
若提问,请务必描述清楚该问题的前因后果,提供尽可能多的对分析该问题有帮助的线索。

小伙伴们发表了 15 条评论

  1. 自定义字体时,例如方正悠宋,发现字体有7个字重,把全部字重都放进去。在kindle 加粗时,发现是直接机械式的加粗字体,而不是匹配到更粗的字重。这种要怎么处理?

    • 自定义字体功能的加粗不会读取字重,如果你想更精细的控制字重,需要自行修改电子书的 CSS 文件。

  2. 来了来了,超级感谢,真是名副其实的书伴,直接从源头解决了我的问题,另外说一下在实操中遇到的问题。
    安装完成fonttools并检测安装成功后。运行提取ttx文件代码就会显示
    “Dumping “新仓耳今楷.ttf” to “新仓耳今楷.ttx”…
    No ”name” table found.”。
    然后仍然会有ttx文件出现,但是内容只有

    <?xml version="1.0" encoding="UTF-8"?>
    <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.22">
    
    </ttFont>

    最后我采取了暴力的办法,直接把你给的正确代码复制了进去,把名称修改过来,然后进行了合并,问题解决,kindle中显示正确。
    但是我再回头对新合并的ttf文件进行提取时,又变成了上边的情况,不过无所谓了,在kindle中不显示名称的问题已经解决了,再次感谢您手把手教我(对于编程和python真的是零基础,只会跟着步骤走)。

    • 好像ttx文件的内容没有显示出来,那我再发一遍

      <?xml version="1.0" encoding="UTF-8"?>
      <ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.22">
      
      </ttFont>
      • 还是没出现,其实就是你给出示例中的头两行和最后一行,中间name开头的每一段都没有

        • 留言会把 XML 代码过滤掉……代码根据你后面留言的描述补上了。

          找不到“name”表是因为文中给出的命令在 Windows 命令提示符中执行有点问题(和类 Unix 系统的终端不同,Windows 命令提示符不能正确识别单引号)。你可以直接把“name”的引号去掉(或者将半角单引号换成半角双引号),应该就可以正常执行了:

          ttx -t name SampleSont.ttf
  3. 求教一个困扰我很久的问题:为什么kindle的邮箱推送会间歇性失灵

    设备:kpw3中亚
    版本:长久以来好多版本都这样

    • 推送稳定性完全取决于亚马逊的个人文档服务器,如果你的 Kindle 设备联网正常却无法收到推送,大部分情况下是亚马逊服务器的问题,用户端没有什么可操作的空间。

        • 如果排版没有通过手工处理,一般都不怎么样。用不了词典有什么具体表现吗?一般情况下,所有电子书都可以使用字典。

            • 用的哪个词典?会不会是你要查的字词字典里不存在。你可以查一下字典里存在的字词,看是否正常。如果还不行的话,可以把查词有问题的电子书发一份到书伴邮箱(页面底部“联系”处获取),书伴实机测试一下。