Python Pillow如何获取带换行文本尺寸实现框内居中
Pillow 矩形内多行/单行文本自动缩放居中实现
问题表现
- 参考常规Pillow文本居中方案实现时,不带换行符的单行文本可以正常匹配矩形尺寸、居中渲染
- 文本中加入
\n换行符后,出现字号适配错误、文本居中偏移的问题 - 问题根因:Pillow 7.1.2版本的
FreeTypeFont.getsize()方法不会识别换行符,会把带换行的多行文本识别为单行计算宽高,返回的尺寸和实际渲染结果完全不符 - 适用场景:制作游戏卡牌等需要同时兼容单行、多行文本自动适配居中的需求
问题复现
正常运行的单行文本代码
title_text = "Hello World" img = Image.new(size=(400, 300), mode='RGB') draw = ImageDraw.Draw(img) font_path = "/content/Charlie don't surf.ttf" # 绘制200x100的白色矩形,中心点坐标为(200,150) draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white') draw.line(((0, 150), (400, 150)), 'gray') draw.line(((200, 0), (200, 300)), 'gray') # 遍历查找能让文本适配200x100矩形的最大字号 selected_size = 1 for size in range(1, 150): title_font = ImageFont.FreeTypeFont(font_path, size=size) w, h = title_font.getsize(title_text) if w > 200 or h > 100: break selected_size = size # 文本居中绘制 title_font = ImageFont.FreeTypeFont(font_path, size=selected_size) draw.text((200-(w//2), 150-h//2), title_text, fill='red', font=title_font) display(img)
运行效果:
异常的多行文本代码
仅把title_text修改为带换行的"Hello\nWorld",其余逻辑不变:
title_text = "Hello\nWorld" img = Image.new(size=(400, 300), mode='RGB') draw = ImageDraw.Draw(img) font_path = "/content/Charlie don't surf.ttf" # 绘制200x100的白色矩形,中心点坐标为(200,150) draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white') draw.line(((0, 150), (400, 150)), 'gray') draw.line(((200, 0), (200, 300)), 'gray') # 遍历查找能让文本适配200x100矩形的最大字号 selected_size = 1 for size in range(1, 150): title_font = ImageFont.FreeTypeFont(font_path, size=size) w, h = title_font.getsize(title_text) if w > 200 or h > 100: break selected_size = size # 文本居中绘制 title_font = ImageFont.FreeTypeFont(font_path, size=selected_size) draw.text((200-(w//2), 150-h//2), title_text, fill='red', font=title_font) display(img)
运行效果:
修复方案
核心逻辑是自行拆分多行文本计算总尺寸,不要直接用getsize()处理带换行符的整段文本,步骤如下:
- 用
split('\n')把文本拆分为单行列表 - 逐行计算单行宽高:总宽度取所有行的宽度最大值,总高度为所有行的高度累加值(如果需要加行间距,可以额外加上
(行数-1)*行间距数值) - 字号适配阶段用上述方法计算得到的多行总宽高和矩形尺寸对比,筛选最大可用字号
- 绘制阶段逐行渲染:先计算整个文本块的左上角起始坐标,每绘制完一行就向下偏移一个行高的距离,每行单独计算水平居中位置
修复后可兼容单行/多行的完整代码
def get_multiline_text_size(font, text, line_spacing=0): """计算带换行符的文本实际渲染宽高""" lines = text.split('\n') max_width = 0 total_height = 0 line_heights = [] for line in lines: w, h = font.getsize(line) max_width = max(max_width, w) line_heights.append(h) total_height += h total_height += line_spacing * (len(lines) - 1) return max_width, total_height, line_heights # 测试文本,可替换为单行/任意行数的带换行文本 title_text = "Hello\nWorld" rect_w, rect_h = 200, 100 rect_center_x, rect_center_y = 200, 150 img = Image.new(size=(400, 300), mode='RGB') draw = ImageDraw.Draw(img) font_path = "/content/Charlie don't surf.ttf" # 绘制参考矩形和辅助线 draw.rectangle((rect_center_x - rect_w//2, rect_center_y - rect_h//2, rect_center_x + rect_w//2, rect_center_y + rect_h//2), fill='white') draw.line(((0, rect_center_y), (400, rect_center_y)), 'gray') draw.line(((rect_center_x, 0), (rect_center_x, 300)), 'gray') # 查找适配矩形的最大字号 selected_size = 1 line_spacing = 4 # 行间距,可根据需求调整 for size in range(1, 150): title_font = ImageFont.FreeTypeFont(font_path, size=size) w, h, _ = get_multiline_text_size(title_font, title_text, line_spacing) if w > rect_w or h > rect_h: break selected_size = size # 逐行居中绘制文本 title_font = ImageFont.FreeTypeFont(font_path, size=selected_size) total_w, total_h, line_heights = get_multiline_text_size(title_font, title_text, line_spacing) # 文本块左上角起始坐标 start_x = rect_center_x - total_w//2 start_y = rect_center_y - total_h//2 current_y = start_y lines = title_text.split('\n') for idx, line in enumerate(lines): line_w, _ = title_font.getsize(line) # 每行单独水平居中 draw.text((start_x + (total_w - line_w)//2, current_y), line, fill='red', font=title_font) current_y += line_heights[idx] + line_spacing display(img)
上述代码不管传入的是无换行的单行文本,还是带任意数量换行的多行文本,都可以自动计算适配的最大字号,在矩形内完美居中。
内容的提问来源于stack exchange,提问作者user3347814
相关产品推荐
相关产品推荐

