查找特定字体支持哪些字符

84

我如何从Linux上的TrueType或嵌入式OpenType字体中提取支持的Unicode字符列表?

是否有工具或库可用于处理.ttf或.eot文件并构建字体提供的代码点列表(例如U+0123,U+1234等)?


6
尝试使用fc-list :charset=1234命令,并仔细检查其输出结果...(对我来说有效,它显示Gentium的字符集为2082但不是2161)。 - mirabilos
1
@mirabilos 这不是问题所问的。它显示包含给定字符(即1234)的字体。 - Neil Mayhew
没错。但是这两个问题是交织在一起的(而且你会在“回答”部分找到许多对错误问题的答案)。 - mirabilos
@mirabilos 很好的观点。我稍微编辑了标题,以使问题的意图更加明显。 - Neil Mayhew
在UNIX.SE上有同样的问题:[fonts - 如何查找TTF文件中定义的Unicode代码点?-Unix&Linux Stack Exchange](https://unix.stackexchange.com/q/247108/296692)--使用`otfinfo`提供答案。 - user202729
14个回答

56

这里是一种使用fontTools Python库的方法(您可以使用类似于pip install fonttools的东西进行安装):

#!/usr/bin/env python
from itertools import chain
import sys

from fontTools.ttLib import TTFont
from fontTools.unicode import Unicode

with TTFont(
    sys.argv[1], 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=-1
) as ttf:
    chars = chain.from_iterable(
        [y + (Unicode[y[0]],) for y in x.cmap.items()] for x in ttf["cmap"].tables
    )
    if len(sys.argv) == 2:  # print all code points
        for c in chars:
            print(c)
    elif len(sys.argv) >= 3:  # search code points / characters
        code_points = {c[0] for c in chars}
        for i in sys.argv[2:]:
            code_point = int(i)   # search code point
            #code_point = ord(i)  # search character
            print(Unicode[code_point])
            print(code_point in code_points)
脚本以字体路径为参数,可选搜索的码点 / 字符。
$ python checkfont.py /usr/share/fonts/**/DejaVuSans.ttf
(32, 'space', 'SPACE')
(33, 'exclam', 'EXCLAMATION MARK')
(34, 'quotedbl', 'QUOTATION MARK')
…

$ python checkfont.py /usr/share/fonts/**/DejaVuSans.ttf 65 12622  # a ㅎ
LATIN CAPITAL LETTER A
True
HANGUL LETTER HIEUH
False

1
在大多数情况下,int(sys.argv[2], 0) 可能会因为“无效的字面量”而失败,因为一个人可能想要找到特殊字符。应该使用 ord(sys.argv[2].decode('string_escape').decode('utf-8')) 来代替。 - Skippy le Grand Gourou
2
无论如何,基于python-fontconfig的这个脚本似乎要快得多:http://unix.stackexchange.com/a/268286/26952 - Skippy le Grand Gourou
@SkippyleGrandGourou 那句话看起来没问题吗?它将 sys.argv[1] 传递给 TTFont() 吗? - Martin Tournoij
1
你可以通过以下方式简化代码: chars = list(y + (Unicode[y[0]],) for x in ttf["cmap"].tables for y in x.cmap.items()) 替换原有代码: chars = chain.from_iterable([y + (Unicode[y[0]],) for y in x.cmap.items()] for x in ttf["cmap"].tables) - Ismael EL ATIFI

41

X程序xfd可以实现这个功能。要查看“DejaVu Sans Mono”字体的所有字符,请运行:

xfd -fa "DejaVu Sans Mono"

在Debian/Ubuntu中,它包含在x11-utils软件包中,在Fedora/RHEL中是xorg-x11-apps,在Arch Linux中是xorg-xfd。


1
xfd还会提供十六进制值,就像你需要在Unicode中键入的那样,比如ctrl+shift+u。 - euxneks
29
打开图形用户界面字符映射并不等同于列出支持的字符。 - rspeer
我想知道是否可以对内置的位图字体(如6x13)进行类似的操作? - domsson
2
很遗憾,这仅适用于已安装的字体。在安装字体之前获取此列表将非常方便。 - Lennart Regebro
这会为不支持的字符显示空矩形。 - Jānis Elmeris

23

fontconfig 命令可以将字形列表输出为紧凑的范围列表,例如:

$ fc-match --format='%{charset}\n' OpenSans
20-7e a0-17f 192 1a0-1a1 1af-1b0 1f0 1fa-1ff 218-21b 237 2bc 2c6-2c7 2c9
2d8-2dd 2f3 300-301 303 309 30f 323 384-38a 38c 38e-3a1 3a3-3ce 3d1-3d2 3d6
400-486 488-513 1e00-1e01 1e3e-1e3f 1e80-1e85 1ea0-1ef9 1f4d 2000-200b
2013-2015 2017-201e 2020-2022 2026 2030 2032-2033 2039-203a 203c 2044 2070
2074-2079 207f 20a3-20a4 20a7 20ab-20ac 2105 2113 2116 2120 2122 2126 212e
215b-215e 2202 2206 220f 2211-2212 221a 221e 222b 2248 2260 2264-2265 25ca
fb00-fb04 feff fffc-fffd

对于一个 .ttf 文件使用 fc-query 命令,对于已安装的字体名称使用 fc-match

这可能不需要安装额外的软件包,也不涉及位图转换。

使用命令 fc-match --format='%{file}\n' 来检查是否匹配了正确的字体。


这是错误的:它说“Gentium Italic”有“2150-2185”等内容,但2161绝对不在其中。 - mirabilos
2
@mirabilos,我有Gentium 5.000字体,它确实包含2161:ttx -t cmap -o - /usr/share/fonts/truetype/GentiumPlus-I.ttf | grep 0x2161 返回 <map code="0x2161" name="uni2161"/><!-- ROMAN NUMERAL TWO -->。可能是FontConfig匹配到了不同的字体。在我安装gentium之前,fc-match 'Gentium Italic'返回FreeMono.ttf: "FreeMono" "Regular"。如果是这样的话,--format=%{charset}的输出将不会显示您期望的内容。 - Neil Mayhew
我添加了一条注释,提到需要检查是否匹配了正确的字体。 - Neil Mayhew
Gentium Plus ≠ Gentium(我安装了所有三种字体,包括正常、基本和Plus,但我想知道Gentium的情况)-啊,算了,我看到问题了:$ fc-match --format ='%{file} \ n' Gentium / usr / share / fonts / truetype / gentium / Gentium-R.ttf $ fc-match --format ='%{file} \ n' Gentium\ Italic / usr / share / fonts / truetype / dejavu / DejaVuSans.ttf $ fc-match --format ='%{file} \ n' Gentium:Italic / usr / share / fonts / truetype / gentium / Gentium-I.ttf而且 fc-match --format ='%{file}⇒%{charset} \ n' Gentium:Italic DTRT,太棒了。 - mirabilos
1
很高兴它对你有用。关于使用Gentium:Italic而不是Gentium Italic的好建议,谢谢你。 - Neil Mayhew

19

16

这是一个基于 POSIX[1] 的shell脚本,可以通过 fc-match 帮助您以简单易懂的方式打印代码点和字符。Neil Mayhew在他的回答中提到了这个工具(它甚至可以处理多达8位十六进制Unicode):

#!/bin/bash
for range in $(fc-match --format='%{charset}\n' "$1"); do
    for n in $(seq "0x${range%-*}" "0x${range#*-}"); do
        n_hex=$(printf "%04x" "$n")
        # using \U for 5-hex-digits
        printf "%-5s\U$n_hex\t" "$n_hex"
        count=$((count + 1))
        if [ $((count % 10)) = 0 ]; then
            printf "\n"
        fi
    done
done
printf "\n"

您可以传递字体名称或任何fc-match接受的内容:
$ ls-chars "DejaVu Sans"

更新内容:

我了解到子shell非常耗时(脚本中的printf子shell)。因此,我成功编写了一个改进版本,速度快了5-10倍!

#!/bin/bash
for range in $(fc-match --format='%{charset}\n' "$1"); do
    for n in $(seq "0x${range%-*}" "0x${range#*-}"); do
        printf "%04x\n" "$n"
    done
done | while read -r n_hex; do
    count=$((count + 1))
    printf "%-5s\U$n_hex\t" "$n_hex"
    [ $((count % 10)) = 0 ] && printf "\n"
done
printf "\n"

旧版本:

$ time ls-chars "DejaVu Sans" | wc
    592   11269   52740

real    0m2.876s
user    0m2.203s
sys     0m0.888s

新版本(行号表示5910+个字符,在0.4秒内完成!):
$ time ls-chars "DejaVu Sans" | wc
    592   11269   52740

real    0m0.399s
user    0m0.446s
sys     0m0.120s

更新结束

示例输出(在我的 st 终端中对齐得更好):

0020    0021 !  0022 "  0023 #  0024 $  0025 %  0026 &  0027 '  0028 (  0029 )
002a *  002b +  002c ,  002d -  002e .  002f /  0030 0  0031 1  0032 2  0033 3
0034 4  0035 5  0036 6  0037 7  0038 8  0039 9  003a :  003b ;  003c <  003d =
003e >  003f ?  0040 @  0041 A  0042 B  0043 C  0044 D  0045 E  0046 F  0047 G
...
1f61a 1f61b 1f61c 1f61d 1f61e 1f61f 1f620 1f621 1f622 1f623
1f625 1f626 1f627 1f628 1f629 1f62a 1f62b 1f62d 1f62e 1f62f
1f630 1f631 1f632 1f633 1f634 1f635 1f636 1f637 1f638 1f639
1f63a 1f63b 1f63c 1f63d 1f63e 1f63f 1f640 1f643

[1] 看起来在 printf 中使用的 \U 不符合 POSIX 标准?


1
#!/bin/sh => #!/bin/bash - vatosarmat
@vatosarmat,没错,应该是类似于bash的东西,谢谢。我猜前者对我来说可行,因为shell使用可执行的printf而不是shell内置的。 - Lu Xu
@CameronKerr,因此在printf行中添加像“\ U0 $ n_hex”这样的“0”对于Ubuntu 20.04而言是有效的吗? - Lu Xu
或者,如果在您的情况下\U至少需要6个字符,那么printf "\U0030"是否甚至无法按预期工作?我确实没有在除Arch之外的其他系统上测试过脚本。 - Lu Xu
1
嗯,'\U0030' 会输出 '0',而 '\U0030 ' 会输出 '0 '。'\U0030a' 输出的是 '\u030a'(前导零,以4位数的\u进行规范化)。但是,正如其他人指出的那样,这是bash内置的,而不是POSIX printf。/usr/bin/printf '\U0030' 会显示 'missing hexadecimal number in escape',而 /usr/bin/printf '\u0030' 则会显示 'invalid universal character name \u0030',但这只是因为应该指定为'0'。http://gnu-coreutils.7620.n7.nabble.com/usr-bin-printf-invalid-universal-character-name-td11992.html - Cameron Kerr
显示剩余4条评论

13

ttf/otf字体的字符编码点存储在CMAP表中。

您可以使用ttx生成CMAP表的XML表示形式,详情请参见此处

您可以运行命令ttx.exe -t cmap MyFont.ttf,它应该会输出一个名为MyFont.ttx的文件。在文本编辑器中打开它,它将向您显示字体中找到的所有字符编码。


请注意,ttx是接受答案中提到的fonttools的一部分。它是一个Python脚本,因此也可在Mac和Linux上使用。 - mivk
1
你可以使用-o -参数让ttx在标准输出中显示输出。例如, ttx -o - -t cmap myfont.ttf 将会把字体文件myfont.ttf中的cmap表格内容转储到标准输出。然后,你可以将其用于查看一个给定字符是否定义在给定字体中(例如 $ font ttx -o - -t cmap myfont.ttf | grep '5c81')。 - rdrg109

5
我刚遇到了同样的问题,并制作了一个更进一步的操作指南,其中包含所有支持的Unicode代码点的正则表达式。
如果您只想要代码点数组,在运行ttx -t cmap myfont.ttf并可能将myfont.ttx重命名为myfont.xml以调用Chrome的xml模式后,在Chrome开发工具中查看您的ttx xml时,可以使用此代码。
function codepoint(node) { return Number(node.nodeValue); }
$x('//cmap/*[@platformID="0"]/*/@code').map(codepoint);

(还依赖于gilamesh的建议中的fonttools;如果您在ubuntu系统上,请运行sudo apt-get install fonttools。)

3

除了 @Oliver Lew 的回答,我还添加了一个选项,可以查询本地字体而不是系统字体:

#!/bin/bash

# If the first argument is a font file, use fc-match instead of fc-query to
# display the font
[[ -f "$1" ]] && fc='fc-query' || fc='fc-match'

for range in $($fc --format='%{charset}\n' "$1"); do
    for n in $(seq "0x${range%-*}" "0x${range#*-}"); do
        printf "%04x\n" "$n"
    done
done | while read -r n_hex; do
    count=$((count + 1))
    printf "%-5s\U$n_hex\t" "$n_hex"
    [ $((count % 10)) = 0 ] && printf "\n"
done
printf "\n"

1
上面的Janus的答案(https://dev59.com/i2855IYBdhLWcg3wPBtx#19438403)是可行的。但是Python太慢了,特别是对于亚洲字体。在我的E5电脑上,处理一个40MB字体文件需要几分钟。
所以我写了一个小的C++程序来做这个。它依赖于FreeType2(https://www.freetype.org/)。这是一个vs2015项目,但很容易移植到Linux,因为它是一个控制台应用程序。
代码可以在这里找到,https://github.com/zhk/AllCodePoints 对于40MB的亚洲字体文件,在我的E5电脑上大约需要30毫秒。

0

你可以在Linux上使用Perl的Font::TTF模块来完成这个任务。


2
是的,这应该是可能的。但它是一个复杂的模块套件,文档也很糟糕。因此,如果没有如何完成它的示例,这个答案似乎相当无用。 - mivk
我认为这不算是一个答案。 - Hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接