143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
from PIL import Image, ImageDraw, ImageFont
|
|
import io
|
|
|
|
def create_text_image(text: str, width: int = 1000, font_size: int = 25) -> bytes:
|
|
"""将文本转换为图片,智能处理各种字符"""
|
|
def load_fonts():
|
|
"""加载文本和 Emoji 字体"""
|
|
# 尝试加载 Emoji 字体
|
|
emoji_font = None
|
|
try:
|
|
emoji_font = ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", font_size)
|
|
print("成功加载 Emoji 字体")
|
|
except Exception as e:
|
|
print(f"加载 Emoji 字体失败: {e}")
|
|
|
|
# 尝试加载文本字体
|
|
text_font = None
|
|
font_paths = [
|
|
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
"/usr/share/fonts/wqy-microhei/wqy-microhei.ttc",
|
|
"/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",
|
|
"C:/Windows/Fonts/msyh.ttc",
|
|
"C:/Windows/Fonts/simhei.ttf",
|
|
]
|
|
|
|
for path in font_paths:
|
|
try:
|
|
text_font = ImageFont.truetype(path, font_size)
|
|
print(f"成功加载文本字体: {path}")
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
if text_font is None:
|
|
text_font = ImageFont.load_default()
|
|
print("使用默认字体")
|
|
|
|
return text_font, emoji_font
|
|
|
|
def is_emoji(char):
|
|
"""判断字符是否为 Emoji"""
|
|
return len(char.encode('utf-8')) >= 4
|
|
|
|
def draw_text_with_fonts(draw, text, x, y, text_font, emoji_font):
|
|
"""使用不同的字体绘制文本和 Emoji"""
|
|
current_x = x
|
|
for char in text:
|
|
# 选择合适的字体
|
|
font = emoji_font if (is_emoji(char) and emoji_font) else text_font
|
|
|
|
# 绘制字符
|
|
draw.text((current_x, y), char, font=font, fill=(0, 0, 0))
|
|
|
|
# 计算字符宽度
|
|
bbox = draw.textbbox((current_x, y), char, font=font)
|
|
char_width = bbox[2] - bbox[0]
|
|
current_x += char_width
|
|
|
|
return current_x - x
|
|
|
|
def calculate_text_dimensions(text, text_font, emoji_font):
|
|
"""计算文本尺寸"""
|
|
test_img = Image.new('RGB', (1, 1), color=(255, 255, 255))
|
|
test_draw = ImageDraw.Draw(test_img)
|
|
|
|
total_width = 0
|
|
max_height = 0
|
|
|
|
for char in text:
|
|
font = emoji_font if (is_emoji(char) and emoji_font) else text_font
|
|
bbox = test_draw.textbbox((0, 0), char, font=font)
|
|
char_width = bbox[2] - bbox[0]
|
|
char_height = bbox[3] - bbox[1]
|
|
total_width += char_width
|
|
max_height = max(max_height, char_height)
|
|
|
|
return total_width, max_height
|
|
|
|
# 加载字体
|
|
text_font, emoji_font = load_fonts()
|
|
|
|
# 基础配置
|
|
padding = 40
|
|
effective_width = width - (2 * padding)
|
|
|
|
def smart_text_wrap(text):
|
|
"""智能文本换行"""
|
|
lines = []
|
|
current_line = ""
|
|
current_width = 0
|
|
|
|
for paragraph in text.split('\n'):
|
|
if not paragraph:
|
|
lines.append("")
|
|
continue
|
|
|
|
for char in paragraph:
|
|
char_width, _ = calculate_text_dimensions(char, text_font, emoji_font)
|
|
|
|
if current_width + char_width <= effective_width:
|
|
current_line += char
|
|
current_width += char_width
|
|
else:
|
|
if current_line:
|
|
lines.append(current_line)
|
|
current_line = char
|
|
current_width = char_width
|
|
|
|
if current_line:
|
|
lines.append(current_line)
|
|
current_line = ""
|
|
current_width = 0
|
|
|
|
return lines
|
|
|
|
# 智能换行处理
|
|
lines = smart_text_wrap(text)
|
|
|
|
# 计算行高
|
|
_, line_height = calculate_text_dimensions("测试", text_font, emoji_font)
|
|
line_spacing = int(line_height * 0.5) # 行间距为行高的50%
|
|
total_line_height = line_height + line_spacing
|
|
|
|
# 计算总高度
|
|
total_height = (len(lines) * total_line_height) + (2 * padding)
|
|
height = max(total_height, 200) # 最小高度200像素
|
|
|
|
# 创建图片
|
|
image = Image.new('RGB', (width, int(height)), color=(252, 252, 252))
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
# 绘制文本
|
|
y = padding
|
|
for line in lines:
|
|
if line: # 跳过空行
|
|
draw_text_with_fonts(draw, line, padding, y, text_font, emoji_font)
|
|
y += total_line_height
|
|
|
|
# 转换为字节流
|
|
img_byte_arr = io.BytesIO()
|
|
image.save(img_byte_arr, format='PNG', quality=95)
|
|
img_byte_arr.seek(0)
|
|
return img_byte_arr.getvalue() |