如何在Python中获取文本字符串的可视长度

10
这个问题类似,我不是在问如何找到字符串中的字符数。 我想确定一个字符串在渲染时的视觉长度或将其与另一个字符串进行比较。
例如,'iiii'和'WWWW'都有四个字符。 但是,'iiii'在视觉上更短。 我知道这是由字体决定的,并且我没有使用等宽字体。 因此,为了解决这个问题,我将使用Arial 10pt。
是否有任何内置模块可以提供给定字体的字符串的视觉尺寸?

我重新表述了你的问题,用“可视”长度代替“物理”长度,因为我认为这更清晰易懂——如果你不同意,可以随时恢复我的更改。 - icktoofay
1
“物理长度”这个术语并不存在;您所说的是使用比例字体时的“显示”或“视觉”长度。我们在谈论哪种GUI或图像生成系统?测量字体度量取决于所使用的框架。 - Martijn Pieters
唯一真正了解某个东西在屏幕上如何呈现的方法是要么在屏幕上计算并获取像素,要么在虚拟渲染环境中预先渲染它(就像无头浏览器的工作方式)。 - user1467267
@alexis,谢谢!这正是我在寻找的。我想Helevetica字体与Arial非常接近,所以使用那种字体可能就足够了。 - Fezter
添加了一个带有afm和tkinter解决方案的答案。Tkinter可以计算Arial字体的宽度(但需要一个tkinter窗口,尽管它不会渲染任何内容)。 - alexis
显示剩余4条评论
4个回答

10

不必渲染成图像缓冲区并计算像素,您可以通过使用字体度量直接计算宽度。核心Python没有分发字体API,但各种包中有许多第三方API可用。这里是一个相当完整的解决方案,使用matplotlib来计算Adobe字体度量:

>>> from matplotlib import rcParams
>>> import os.path

>>> afm_filename = os.path.join(rcParams['datapath'], 'fonts', 'afm', 'ptmr8a.afm')
>>>
>>> from matplotlib.afm import AFM
>>> afm = AFM(open(afm_filename, "rb"))
>>> afm.string_width_height('What the heck?')
(6220.0, 694)

这些指标以正在使用的字体比例尺(点大小)的1/1000为单位报告。(感谢@JacobLee挖掘了这个信息。)
另一个可能性是tkintertkFont模块。此页面记录了函数tkFont.Font.measure("some string"),但似乎需要一个Tk窗口才能使用它;所以我不知道它有多实用:
# Python 3 names -- see Note below
import tkinter 
from tkinter import font as tkFont

tkinter.Frame().destroy()  # Enough to initialize resources
arial36b = tkFont.Font(family='Arial', size=36, weight='bold')
width = arial36b.measure("How wide is this?")
print(width)  # Prints: 404

注意:在Python 2中(以及我上面提到的page中),tkinter被称为Tkinter,而tkinter.font是一个顶级模块,tkFont
import Tkinter
import tkFont

我想从后端检索文本,其中字体大小和显示区域尺寸是从前端发送的。我可以在后端使用matplotlib吗?显然,在后端没有任何显示设备。 - Mohammed Shareef C
请问Matplotlib返回的值(宽度和高度)的单位是什么?像素?毫米? - Mohammed Shareef C
1
matplotlib.afm文档中提到:“与Adobe字体度量文件格式规范一样,所有尺寸都以所使用的字体的比例因子(点大小)的1/1000为单位给出。” - Jacob Lee
我能将这种方法应用于任意字体吗?我认为我感兴趣的字体应该有afm文件。或者可能有一些应用程序可以为任意字体生成afm文件。我刚刚发现了这个 - KH Kim
如果你能找到或生成你的字体的afm度量标准,那么我相信你可以做到。 - alexis
显示剩余3条评论

5
如果您正在使用Windows,则可以使用以下方法。它将当前屏幕用作输出上下文,并计算显示给定字体的给定点大小所需的尺寸。它返回一个元组,其中包含文本宽度和文本高度:
import ctypes

def GetTextDimensions(text, points, font):
    class SIZE(ctypes.Structure):
        _fields_ = [("cx", ctypes.c_long), ("cy", ctypes.c_long)]

    hdc = ctypes.windll.user32.GetDC(0)
    hfont = ctypes.windll.gdi32.CreateFontA(-points, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, font)
    hfont_old = ctypes.windll.gdi32.SelectObject(hdc, hfont)
    size = SIZE(0, 0)
    ctypes.windll.gdi32.GetTextExtentPoint32A(hdc, text, len(text), ctypes.byref(size))
    ctypes.windll.gdi32.SelectObject(hdc, hfont_old)
    ctypes.windll.gdi32.DeleteObject(hfont)
    return (size.cx, size.cy)

for text, font in [
    ('....', 'Arial'), 
    ('WWWW', 'Arial'), 
    ('WWWW', 'Arial Narrow'),
    ('....', 'Courier New'), 
    ('WWWW', 'Courier New'), 
    ("Test", "Unknown font"),
    ('Test', 'Calibri')]:

    print '{:8} {:20} {}'.format(text, font, GetTextDimensions(text, 12, font))

这将显示以下输出:
....     Arial                (12, 15)
WWWW     Arial                (44, 15)
WWWW     Arial Narrow         (36, 16)
....     Courier New          (28, 15)
WWWW     Courier New          (28, 15)
Test     Unknown font         (24, 15)
Test     Calibri              (23, 14)

Arial作为一种比例字体,在“...”和“WWWW”上显示不同的尺寸,但Courier New是固定宽度的,因此结果相同。与Arial相比,Arial Narrow给出了36而不是44。
在“Unknown font”的情况下,Windows字体映射器会自动选择默认字体。
测试过适用于Python 2.x。
注意:对于Python 3.x版本,由于在Windows中调用了GetTextExtentPoint32A(),因此需要传递ANSI文本。可以更改调用方式来解决这个问题。
ctypes.windll.gdi32.GetTextExtentPoint32A(hdc, text.encode('cp1252'), len(text), ctypes.byref(size))

或者,将代码切换为使用宽字符版本,并替换为以下两个:

hfont = ctypes.windll.gdi32.CreateFontW(-points, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, font)
ctypes.windll.gdi32.GetTextExtentPoint32W(hdc, text, len(text), ctypes.byref(size))

我不确定这是否是一个完整的解决方案:
GetTextDimensions("测试",12,"Calibri") (32, 15) GetTextDimensions("测试",12,"Arial") (32, 15) GetTextDimensions("测试",12,"Courier New") (32, 15) GetTextDimensions("测试",12,"假的字体") (32, 15)
- gss
1
@fearwig,我猜你是在Python 3.x上尝试这个?因为它调用了一个ANSI Windows函数,所以文本首先必须被编码。例如,传递text.encode('cp1252') - Martin Evans

0

使用图形/字体库,例如ImageFont。绘制字符串,然后使用getsize获取宽度。

请注意,一些文本如“AWAY”由于kerning可能比单个字母的总和更窄。因此查找每个字母的宽度并将它们相加会很困难。


我相信这就是问题所在 - 由于某些字体的字距调整,字符串的宽度将与单个字形的宽度之和不同。 - sophros

0
只是对@alexis的tkinter答案进行了小改进。 我发现这种方法简单可靠,除了它会打开一个你必须手动关闭的tkinter窗口。 我修改了代码,使你不再需要窗口,并创建了一个带有更多选项的函数。
from tkinter import Tk
from tkinter.font import Font

def get_text_size(text: str, font_family: str = 'Arial', font_size: int = 10, bold: bool = False) -> int:
    """Get the screen width of a text based on Font Type, Font Size and Font Weight

    Args:
        text (str): Text for which to calculate the screen width
        font_family (str, optional): Font family. Defaults to 'Arial'.
        font_size (int, optional): Font size. Defaults to 10.
        bold (bool, optional): If bold or not. Defaults to False.

    Returns:
        int: Screen width of the text
    """
    root = Tk()  # Needed to estimate the width.
    font_weight = 'bold' if bold else 'normal'
    font_var = Font(family=font_family, size=font_size, weight=font_weight)
    width = font_var.measure(text)
    root.destroy()  # Destroy the created window
    return width

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