Python中Pillow渲染Font Awesome图标显示空方块的解决方法
我正在解析包含Font Awesome图标的HTML页面,示例图标映射如下:
FA_ICONS = { "fa-calendar": "📅", "fa-times-circle": "✖", "fa-check-circle": "✔", "fa-info-circle": "ℹ", "fa-exclamation-circle": "❗", "fa-exclamation-triangle": "⚠", "fa-lightbulb-o": "💡", "fa-bolt": "⚡", #.. etc }
需要在无需用户安装系统字体的前提下,使用Pillow将这些图标渲染为PNG图片。我尝试把图标通过Font Awesome Unicode字符转换为带<text>元素的SVG,再用cairosvg渲染,但输出结果是空白方块(⯀),Windows/Linux/macOS多系统都存在这个问题。我需要可无像素化缩放的矢量级图标,且要支持全部Font Awesome 12图标,而非仅含emoji等效项的子集。基于当前技术栈(Pillow、cairosvg),如何正确将Font Awesome图标渲染为PNG图片?
相关代码:
from PIL import Image import cairosvg import io def render_fa_icon(icon_unicode: str = "\uf005") -> io.BytesIO: """Render Font Awesome icon by Unicode character""" svg = f''' <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"> <text x="12" y="18" font-family="Font Awesome 6 Free" font-weight="900" font-size="20" text-anchor="middle" fill="black">{icon_unicode}</text> </svg> ''' png_bytes = cairosvg.svg2png(bytestring=svg.encode('utf-8')) img = Image.open(io.BytesIO(png_bytes)) img.show() # Shows empty square img_io = io.BytesIO() img.save(img_io, format='PNG') img_io.seek(0) return img_io # 输出空白方块 render_fa_icon("\uf005") # fa-home对应的Unicode
环境信息:
Environment: - OS: Windows 11 / Ubuntu 22.04 - Python: 3.11.5 - Pillow: 10.1.0 - cairosvg: 2.7.0
空白方块的核心原因是cairosvg找不到Font Awesome字体文件——SVG里指定了字体,但系统未安装,也没在SVG中内嵌或指定字体文件路径,导致渲染器无法解析Unicode对应的字形。
要实现无需系统安装字体、矢量级渲染的需求,按以下步骤修改:
1. 准备Font Awesome字体文件
从Font Awesome官方包中提取对应风格的字体文件(比如Font Awesome 6 Free-Solid-900.otf,对应代码里的font-weight:900),将其放在Python脚本可访问的路径下(如脚本同目录)。
2. 修改SVG渲染逻辑
有两种可靠方式让cairosvg识别字体:
方式一:内嵌字体到SVG(推荐,无外部文件依赖)
先将字体文件转换为Base64编码,再内嵌到SVG的<style>标签中:
import base64 from PIL import Image import cairosvg import io def get_font_base64(font_path): with open(font_path, 'rb') as f: return base64.b64encode(f.read()).decode('utf-8') def render_fa_icon(icon_unicode: str = "\uf005", font_path="Font Awesome 6 Free-Solid-900.otf") -> io.BytesIO: font_base64 = get_font_base64(font_path) svg = f''' <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"> <style> @font-face {{ font-family: 'Font Awesome 6 Free'; font-weight: 900; src: url(data:font/otf;base64,{font_base64}) format('opentype'); }} </style> <text x="12" y="18" font-family="Font Awesome 6 Free" font-weight="900" font-size="20" text-anchor="middle" fill="black">{icon_unicode}</text> </svg> ''' png_bytes = cairosvg.svg2png(bytestring=svg.encode('utf-8')) img = Image.open(io.BytesIO(png_bytes)) img.show() img_io = io.BytesIO() img.save(img_io, format='PNG') img_io.seek(0) return img_io
方式二:指定cairosvg的字体搜索目录
如果不想内嵌字体,可通过font_config参数指定字体所在目录:
from PIL import Image import cairosvg from cairosvg import FontConfig import io def render_fa_icon(icon_unicode: str = "\uf005", font_dir="./fonts") -> io.BytesIO: font_config = FontConfig() font_config.fonts_dirs.append(font_dir) # 存放Font Awesome字体的目录 svg = f''' <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"> <text x="12" y="18" font-family="Font Awesome 6 Free" font-weight="900" font-size="20" text-anchor="middle" fill="black">{icon_unicode}</text> </svg> ''' png_bytes = cairosvg.svg2png(bytestring=svg.encode('utf-8'), font_config=font_config) img = Image.open(io.BytesIO(png_bytes)) img.show() img_io = io.BytesIO() img.save(img_io, format='PNG') img_io.seek(0) return img_io
3. 适配与验证
- 确保Unicode字符和字体文件匹配:比如
fa-home(\uf005)属于Solid风格,对应Font Awesome 6 Free-Solid-900.otf;若使用Regular风格图标,需替换为Font Awesome 6 Free-Regular-400.otf,同时修改font-weight为400。 - 微调SVG的
x、y坐标和font-size,让图标在画布中居中(Font Awesome字形基线与普通文本不同,可能需要调整y值)。
更稳定的替代方案:直接渲染Font Awesome原始SVG
若能获取Font Awesome官方包中的原始SVG图标文件(位于svgs目录下),可直接用cairosvg渲染,无需处理字体问题:
from PIL import Image import cairosvg import io def render_fa_svg(svg_path: str) -> io.BytesIO: with open(svg_path, 'rb') as f: png_bytes = cairosvg.svg2png(file_obj=f) img = Image.open(io.BytesIO(png_bytes)) img.show() img_io = io.BytesIO() img.save(img_io, format='PNG') img_io.seek(0) return img_io # 示例:渲染fa-home的SVG文件 render_fa_svg("svgs/solid/home.svg")
这种方式直接使用矢量图形,无需依赖字体渲染,且支持所有Font Awesome图标,稳定性更高。
内容的提问来源于stack exchange,提问作者Versst

