如何使用 Wand 在 Python 中为文本制作自动调整大小的框以保持相同的字体大小

How to make auto-sizing boxes in images for text to remain same font size in Python with Wand

我在 python 中使用 Wand 来创建一个迭代自身的图像,以便它可以创建填充有文本的黑边框。这是我当前的代码:

import re
from unicodedata import normalize

from docx import Document
from wand.image import Image
from wand.drawing import Drawing
from wand.font import Font

doc = Document("P.docx")
docText = []
for para in doc.paragraphs:
    docText.append(para.text)
fullText = "\n".join(docText)

ct = 242


def get(source, begin, end):
    try:
        start = source.index(len(begin)) + len(begin)
        finish = source.index(len(end), len(start))
        return source[start:finish]
    except ValueError:
        return ""


def capitalize(string):
    cap = ("".join(j[0].upper() + j[1:]) for j in string)
    return cap


def find_matches(text):
    return capitalize(
        [
            m
            for m in re.findall(
                r"^[^0-9]\s+([^.;]+\s*)+[.;]+", normalize("NFKD", text), re.MULTILINE
            )
        ]
    )


with Image(width=400, height=1000, pseudo='xc:white') as canvas:
    left, top, width, height = 2, 2, 395, 131
    for match in find_matches(text=fullText):
        ct += 1
        with Drawing() as context:
            context.fill_color = 'black'
            context.rectangle(left=left, top=top, width=width, height=height)
            context.fill_color = 'white'
            context.rectangle(left=(left+2), top=(top+2), width=(width-4), height=(height-4))
            canvas.font = Font('/System/Library/Fonts/timesnewroman.ttf')
            context(canvas)
            canvas.caption(match + '\n' + str(ct), left=(left+5), top=top, width=(width-10), height=height,
                           gravity='center')
        top += 135
    canvas.crop(bottom=top)
    canvas.save(filename='patdrawTest.png')

使用此代码,我可以生成以下输出:

这个问题显然是字体大小不统一。以下是我的输出应该是什么样子的(忽略框中的文本):

我唯一关心的是确保所有文本的字体大小相同(12 号很好),并且那些带边框的框会根据文本调整大小。感谢您的帮助!

编辑

根据提供的答案,这是我当前的代码:

import re
from unicodedata import normalize

from docx import Document
from wand.image import Image
from wand.drawing import Drawing

doc = Document("P.docx")
docText = []
for para in doc.paragraphs:
    docText.append(para.text)
fullText = "\n".join(docText)

ct = 242


def get(source, begin, end):
    try:
        start = source.index(len(begin)) + len(begin)
        finish = source.index(len(end), len(start))
        return source[start:finish]
    except ValueError:
        return ""


def capitalize(string):
    cap = ("".join(j[0].upper() + j[1:]) for j in string)
    return cap


def find_matches(text):
    return capitalize(
        [
            m
            for m in re.findall(
                r"^[^0-9]\s+([^.;]+\s*)+[.;]+", normalize("NFKD", text), re.MULTILINE
            )
        ]
    )


def to_chunks(words, size):
    for idx in range(0, len(words), size):
        yield words[idx:idx + size]


def rebuild_text(words, size):
    return "\n".join([" ".join(w) for w in to_chunks(words, size)])


target_width = 396
target_height = 0
y_offset = 0
y_padding = 10
x_padding = 5

with Image(width=1000, height=1000, pseudo='xc:white') as img:
    for match in find_matches(text=fullText):
        ct += 1
        with Drawing() as ctx:
            ctx.font_size = 16
            ctx.text_alignment = 'center'
            words = match.split(" ")
            words.append("\n" + str(ct))
            word_count = len(words)
            while True:
                temp_text = rebuild_text(words, word_count)
                metrics = ctx.get_font_metrics(img, temp_text, multiline=True)
                if metrics.text_width > target_width:
                    word_count -= 1
                else:
                    text = temp_text
                    target_height = int(metrics.text_height + 0.5)
                    break
            ctx.push()
            ctx.fill_color = 'white'
            ctx.stroke_width = 4
            ctx.stroke_color = 'black'
            ctx.rectangle(0, y_offset + y_padding, width=2*x_padding+target_width,
                          height=2*y_padding+target_height)
            ctx.pop()
            ctx.text(x_padding + (target_width // 2), 4*y_padding+y_offset, text)
            ctx(img)
            y_offset = target_height + 4*y_padding
    img.trim()
    img.save(filename='patdrawdemoTest.png')

从这段代码中,我得到以下输出:

我不太确定如何修复奇怪的间距。第一个和第三个盒子看起来不错,但是第一个和第二个盒子之间有奇怪的白色space,两边的边框不均匀,而且还有两个应该在那里的盒子,如原文所示post。任何帮助将不胜感激!

The only thing that I'm concerned with is making sure that all the text is the same font size (size 12 is great), with those bordered boxes resizing to the text. Thanks for any help!

删除 Image.caption 方法(与您想要的相反),并使用 Drawing.text 方法。 Drawing class 还有一个 get_font_metrics 方法来计算最终渲染大小。这允许您设置文本格式,检查它是否有效,然后绘制(或重新设置格式并重复)。

举个粗略的例子...

content = [
    """Donec pretium vulputate sapien nec sagittis aliquam malesuada. Neque aliquam vestibulum morbi blandit cursus risus at ultrices mi.\n111""",
    """Adipiscing elit ut aliquam purus sit amet luctus venenatis. Eget mauris pharetra et ultrices neque ornare aenean. Viverra orci sagittis eu volutpat odio facilisis mauris. Vitae proin sagittis nisl rhoncus mattis rhoncus. Sapien nec sagittis aliquam malesuada bibendum arcu vitae.\n222"""
]

def to_chunks(words, size):
    for idx in range(0, len(words), size):
        yield words[idx:idx + size]

def rebuild_text(words, size):
    return "\n".join([" ".join(w) for w in to_chunks(words, size)])
TARGET_WIDTH = 395
TARGET_HEIGHT = 0
BOX_Y_OFFSET = 0
BOX_Y_PADDING = 10
BOX_X_PADDING = 5

with Image(width=1000, height=1000, background='white') as img:
    for text in content:
        with Drawing() as ctx:
            ctx.font_size = 16
            ctx.text_alignment = 'center'
            words = text.split(" ")
            word_count = len(words)
            while True:
                tmp_text = rebuild_text(words, word_count)
                metrics = ctx.get_font_metrics(img, tmp_text, multiline=True)
                if metrics.text_width > TARGET_WIDTH:
                    word_count -= 1
                else:
                    text = tmp_text
                    TARGET_HEIGHT = int(metrics.text_height + 0.5)
                    break
            ctx.push()
            ctx.fill_color = 'white'
            ctx.stroke_width = 4
            ctx.stroke_color = 'black'
            ctx.rectangle(0, BOX_Y_OFFSET + BOX_Y_PADDING, width=2*BOX_X_PADDING+TARGET_WIDTH, height=2*BOX_Y_PADDING+TARGET_HEIGHT)
            ctx.pop()
            ctx.text(BOX_X_PADDING + (TARGET_WIDTH // 2), 4*BOX_Y_PADDING+BOX_Y_OFFSET, text)
            ctx(img)
            BOX_Y_OFFSET = TARGET_HEIGHT + 4*BOX_Y_PADDING
            
    img.trim()
    img.save(filename='output.png')

所有这一切都是:

  • 定义目标宽度。
  • 待渲染字体变大时,减少每行字数
  • 画框和文字。

请记住。

  • ImageMagick 不是类型-setter。您必须构建一个单词连字符/换行算法。
  • 特殊的 UTF-8 字符可能成为一个挑战
  • 上面发布的代码是示例,不处理错误或防止递归循环。