如何判断字体是符号字体还是文本字体?

4

我正在构建一个基于Kivy的应用程序,并需要包含一个字体选择器。由于我找不到适合Kivy的字体选择器,所以我正在自己构建。但我无法找到一种方法来判断一个字体是否适用于我(即,我是否可以使用它制作一个句子而不是一串符号)。我正在使用Pillow ImageFont。在Python中是否有区分符号和文本字体的方法?


1
这是一个常见的问题,即使对于“普通”字体也是如此。目前的ImageFont类中似乎没有任何有用的东西。我会说,“接受它”,并让后果由自愿选择这种字体的用户承担。谁知道,他们甚至可能想要 WingDings。 - Jongware
我不认为这是可能的,我曾经试图找到一种类似的做法,但却无法实现。 - Lissy93
1
你可以使用类似这样的工具来检查支持哪些字符集:https://github.com/davelab6/pyfontaine - 但我知道这并没有真正回答你的问题。 - Lissy93
@Lissy,看起来很有前途,但它只支持Python2。我正在使用Python 3.6。 - John Anderson
使用PANOSE信息是确定所需字体的绝佳方式。不幸的是,我听说并非所有字体都添加了该信息,并且即使添加了该信息,有时也不正确。此外,我不确定如何从字体中提取PANOSE信息... :P - Pepe Ochoa
2个回答

1

我一直在努力,现在我的工作有所改善。虽然还不完美,但以下代码在我的Ubuntu上只给了我一个错误的字体支持文本:

def isValidFont(f, fontNum=0, debug=False):
    """
    Determine if a font is a valid font for displaying text

    This code makes a best guess as to whether the font supports text.
    It does this by writing a 'W' using the font, and inspecting the result.

    :param f: full path to the font file
    :param fontNum: index into the font file (for a file containing a collection of fonts)
    :param debug: If True, files will be written for each font with information useful for debugging
    :return: True if font appears to support text, False otherwise
    """

    # size of test image
    width = 40
    height = 40

    font = ImageFont.truetype(f, index=fontNum, size=height-6)
    fontName = font.getname()
    tmpImg = Image.new('1', (width,height)) #default fill is 0 (black)

    # draw a single 'W' into the test image (sized and positioned to fit inside test image)
    # this codes depends on the character drawn being a 'W'
    dr = ImageDraw.Draw(tmpImg)
    dr.text((3, 3), 'W', font=font, fill=(1))

    if debug:
        # save test image for this font
        fname = str(fontName) + '.bmp'
        tmpImg.save(fname)

    # get the data from the image as a list of 1's and 0's (one value per pixel)
    img_data = list(tmpImg.getdata())

    if debug:
        # write the image data to a file
        fname = str(fontName) + '.txt'
        fd = open(fname, mode='w')
        for row in range(height):
            fd.write(str(img_data[row*width : (row+1)*width]) + '\n')
        fd.close()

    # if the image is all black (0's), this is not a valid text font
    if sum(img_data) == 0:
        return False

    # build a simplified version of the image data
    compressedList = []
    for i in range(height):
        prev_elem = None
        thisRow = []
        for j in range(width):
            index = i*width + j
            elem = img_data[index] # this is the element at (i,j)
            if prev_elem is None:
                # first element in this row, just append to "thisRow"
                thisRow.append(elem)
                prev_elem = elem
            elif elem == prev_elem:
                # if this element is same as previous (and it's a one), just increment the value in "thisRow"
                if elem == 1:
                    thisRow[len(thisRow)-1] += 1
            else:
                # just append the element to "thisRow"
                thisRow.append(elem)
                prev_elem = elem
        # finished row #i, append it to "compressedList"
        compressedList.append(thisRow)

    # a bit more compressing
    for row in compressedList:
        # eliminate leading zeros from each row
        while len(row) > 0 and row[0] == 0:
            del row[0]

        # eliminate trailing zeros from each row
        while len(row) > 0:
            index = len(row)-1
            if row[index] == 0:
                del row[index]
            else:
                break

    # eliminate leading empty rows
    while len(compressedList[0]) == 0:
        del compressedList[0]

    # eliminate trailing empty rows
    index = len(compressedList)-1
    while len(compressedList[index]) == 0:
        del compressedList[index]
        index = len(compressedList)-1

    if debug:
        # save the compressed format
        fname = str(fontName) + '_c.txt'
        fd = open(fname, mode='w')
        for row in compressedList:
            fd.write(str(row) + '\n')
        fd.close()

    # this is where the decision is actually made
    for row in compressedList:
        if len(row) > 3: # characteristic of a 'W', a simple box will have maximum rowLen of 3
            return True
    return False

0

我不是这方面的专家,但是我会给你我的看法。

当你尝试使用某种字体写信时,渲染器将使用该字母的代码点从字体文件中获取绘制指令。有三种情况可能发生:

  1. 代码点的指令存在,但包含错误的图像。例如,对于代码点0x0041,字体包含绘制某个表情符号的指令,而不是绘制拉丁大写字母"A"的指令。
  2. 代码点的指令存在并包含正确的图像。
  3. 代码点的指令不存在。

在第一种情况下,你没有选择:只有人类(或非常高级的脚本)才能说“这一堆像素包含表情符号而不是字母”。但这种情况应该很少见:这不是(正常)人们创建符号字体的方式。

第二种情况很简单:这种字体没问题,你可以使用它。

第三种情况比较有趣。有时人们会保留 Unicode 图像的代码点(例如),但删除字母的指令以减小字体文件的大小。 ImageFont 似乎没有特定的方法可以按代码点提取信息,但它 允许我们 获取文本掩模。可以由字体绘制的文本的掩模将具有 (0, 0) 大小:

from PIL import ImageFont


font = ImageFont.truetype("font_to_test.ttf")

mask = font.getmask('abcdefg')  # use some letters from lang you need

if mask.size == (0, 0):
    print('This is image font.')
else:
    print('This is text font.')

不是很理想,但对我测试的几种字体来说还算可以胜任。


1
那对一些字体起作用,但不够。似乎大多数符号字体为任何文本字符绘制一个框,因此我编写了一个方法来尝试检测它。我创建了一个小的(10x10)1位图像,并将单个字符写入其中(使用Pillow)。然后我进行了一些非常丑陋的启发式算法来尝试检测图像中的框。看起来可以工作,但这是一个如此丑陋的黑客行为,以至于我不会发布它。 - John Anderson

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