为TextBoxAutoWrap类添加右对齐功能,优化字符宽度计算方法,简化绘制逻辑。

This commit is contained in:
Chenyang Zan
2025-05-14 13:35:41 +08:00
parent 0982ea3170
commit 189baaa0b6
+58 -34
View File
@@ -22,6 +22,8 @@ class TextBoxAutoWrap:
"stroke_hex": ("STRING", {"default": "#000000FF"}), # 描边颜色
"enable_small_caps": ("BOOLEAN", {"default": False}),
"small_caps_scale": ("FLOAT", {"default": 0.8, "min": 0.5, "max": 1.0}),
# — 新增 ——
"align_right": ("BOOLEAN", {"default": False}),
}
}
@@ -36,7 +38,30 @@ class TextBoxAutoWrap:
return a + d
@staticmethod
def char_font(c, font, small_font, enable_small_caps):
"""返回绘制字符 c 时应使用的 ImageFont 对象"""
return (
small_font if (enable_small_caps and c.isdigit() and small_font) else font
)
@classmethod
def line_pixel_width(
cls, text_line, font, small_font, letter_spacing, enable_small_caps
):
"""计算单行像素宽度(含字间距,末尾不加)"""
if not text_line:
return 0
width = 0
for idx, ch in enumerate(text_line):
fnt = cls.char_font(ch, font, small_font, enable_small_caps)
width += fnt.getlength(ch)
if idx < len(text_line) - 1:
width += letter_spacing
return width
@classmethod
def wrap_by_pixel_with_spacing(
cls,
text,
font,
max_w,
@@ -50,16 +75,10 @@ class TextBoxAutoWrap:
for ch in text.replace("\n", ""):
test = buf + ch
width = 0
for c in test:
is_digit = c.isdigit()
use_font = (
small_font
if (enable_small_caps and is_digit and small_font)
else font
)
width += use_font.getlength(c)
width += max(0, len(test) - 1) * letter_spacing
# 计算 test 的宽度
width = cls.line_pixel_width(
test, font, small_font, letter_spacing, enable_small_caps
)
if width <= max_w:
buf = test
@@ -86,29 +105,30 @@ class TextBoxAutoWrap:
enable_small_caps=False,
small_font=None,
):
"""按字符绘制:数字小字号,其余大字号;保持 baseline 对齐"""
"""数字小字号,其余大字号;baseline 对齐"""
x, y0 = pos
big_a, _ = font.getmetrics()
sml_a, _ = small_font.getmetrics() if small_font else font.getmetrics()
y_offset = max(0, big_a - sml_a)
for char in text:
is_digit = char.isdigit()
use_small = enable_small_caps and is_digit and small_font is not None
for ch in text:
use_small = enable_small_caps and ch.isdigit() and small_font is not None
cur_font = small_font if use_small else font
y = y0 + (y_offset if use_small else 0)
# ---- 描边 ----
# 描边
if stroke_width > 0:
for dx in range(-stroke_width, stroke_width + 1):
for dy in range(-stroke_width, stroke_width + 1):
if dx or dy:
draw.text(
(x + dx, y + dy), char, fill=stroke_fill, font=cur_font
(x + dx, y + dy), ch, fill=stroke_fill, font=cur_font
)
# ---- 主文字 ----
draw.text((x, y), char, fill=fill, font=cur_font)
x += cur_font.getlength(char) + letter_spacing
# 主文字
draw.text((x, y), ch, fill=fill, font=cur_font)
x += cur_font.getlength(ch) + letter_spacing
# ---------- 颜色解析 ----------
@staticmethod
@@ -140,6 +160,7 @@ class TextBoxAutoWrap:
stroke_hex,
enable_small_caps,
small_caps_scale,
align_right,
):
font_rgba = self.hex_to_rgba(font_hex)
stroke_rgba = self.hex_to_rgba(stroke_hex)
@@ -151,7 +172,7 @@ class TextBoxAutoWrap:
else None
)
# 缩放比
# 估算缩放比
char_cnt = len(re.sub(r"\s+", "", text))
chars_per = max(base_chars_per_line, math.ceil(char_cnt / max_lines))
hscale = max(base_chars_per_line / chars_per, min_hscale)
@@ -159,13 +180,7 @@ class TextBoxAutoWrap:
# 换行
lines = self.wrap_by_pixel_with_spacing(
text,
font,
virt_w,
max_lines,
letter_spacing,
enable_small_caps,
small_font,
text, font, virt_w, max_lines, letter_spacing, enable_small_caps, small_font
)
lh = max(self.line_height(font), self.line_height(small_font or font))
@@ -176,12 +191,19 @@ class TextBoxAutoWrap:
d_v = ImageDraw.Draw(img_v)
m_v = ImageDraw.Draw(msk_v)
# 逐行绘制
y = 0
for ln in lines:
# 根据对齐方式确定起始 x
line_width = self.line_pixel_width(
ln, font, small_font, letter_spacing, enable_small_caps
)
x_start = virt_w - line_width if align_right else 0
# 正片
self.draw_text_with_spacing_and_stroke(
d_v,
(0, y),
(x_start, y),
ln,
font,
font_rgba,
@@ -194,7 +216,7 @@ class TextBoxAutoWrap:
# 蒙版
self.draw_text_with_spacing_and_stroke(
m_v,
(0, y),
(x_start, y),
ln,
font,
255,
@@ -206,11 +228,13 @@ class TextBoxAutoWrap:
)
y += lh
# 缩放 & 锐化
img = img_v.resize((box_width, box_height), resample=Image.LANCZOS)
img = img.filter(ImageFilter.SHARPEN)
mask = msk_v.resize((box_width, box_height), resample=Image.NEAREST)
mask = mask.filter(ImageFilter.SHARPEN)
# 缩回目标尺寸 & 锐化
img = img_v.resize((box_width, box_height), resample=Image.LANCZOS).filter(
ImageFilter.SHARPEN
)
mask = msk_v.resize((box_width, box_height), resample=Image.NEAREST).filter(
ImageFilter.SHARPEN
)
img_tensor = novel_pil2tensor(img)
mask_rgb = torch.stack(