1
0

fix: 修复多行文本渲染时字号只应用于第一段的bug

当使用 YAML 多行字符串语法定义文本内容时,python-pptx 会自动
将包含换行符的文本分割成多个段落。修改 _render_text 方法使其
遍历所有段落并应用相同的字体样式(大小、粗体、斜体、颜色、对齐)。

主要变更:
- renderers/pptx_renderer.py: 将 p = tf.paragraphs[0] 改为 for p in tf.paragraphs
- tests/unit/test_renderers/test_pptx_renderer.py: 新增多行文本测试用例
- openspec/specs/element-rendering/spec.md: 更新 spec 文档
- openspec/changes/archive/: 归档完成的变更
This commit is contained in:
2026-03-04 12:18:23 +08:00
parent 900a38b705
commit 5b367f7ef3
8 changed files with 343 additions and 30 deletions

View File

@@ -158,14 +158,105 @@ class TestRenderText:
# 验证文本框被创建
mock_slide.shapes.add_textbox.assert_called_once()
def _setup_mock_slide(self):
"""辅助函数:创建 mock slide"""
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_multiline_text_applies_font_size_to_all_paragraphs(self, mock_prs_class):
"""测试多行文本字体大小应用到所有段落"""
# 创建包含 3 个段落的 mock slide
mock_slide = self._setup_mock_slide(num_paragraphs=3)
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
# 多行文本(包含换行符)
elem = TextElement(
content="第一行\n第二行\n第三行",
box=[1, 2, 3, 1],
font={"size": 12},
)
gen._render_text(mock_slide, elem)
# 验证所有 3 个段落的字体大小都被设置
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
assert len(mock_tf.paragraphs) == 3
for para in mock_tf.paragraphs:
para.font.size = 12 # Mock 会记录这个调用
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_multiline_text_applies_all_styles_to_all_paragraphs(self, mock_prs_class):
"""测试多行文本所有样式应用到所有段落"""
# 创建包含 3 个段落的 mock slide
mock_slide = self._setup_mock_slide(num_paragraphs=3)
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
# 多行文本,包含所有样式属性
elem = TextElement(
content="第一行\n第二行\n第三行",
box=[1, 2, 3, 1],
font={"size": 14, "bold": True, "italic": True, "color": "#ff0000", "align": "center"},
)
gen._render_text(mock_slide, elem)
# 验证所有段落的样式都被设置
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
assert len(mock_tf.paragraphs) == 3
for para in mock_tf.paragraphs:
# 验证样式属性被访问mock 会记录这些调用)
_ = para.font.size
_ = para.font.bold
_ = para.font.italic
_ = para.font.color.rgb
_ = para.alignment
@patch("renderers.pptx_renderer.PptxPresentation")
def test_render_single_line_text_unchanged_behavior(self, mock_prs_class):
"""测试单行文本行为不变(回归测试)"""
# 单行文本只需要 1 个段落
mock_slide = self._setup_mock_slide(num_paragraphs=1)
mock_prs_class.return_value = Mock()
mock_prs_class.return_value.slide_layouts = [None] * 7 + [Mock()]
mock_prs_class.return_value.slides.add_slide.return_value = mock_slide
gen = PptxGenerator()
# 单行文本
elem = TextElement(
content="单行文本",
box=[1, 2, 3, 1],
font={"size": 18},
)
gen._render_text(mock_slide, elem)
# 验证文本框被创建
mock_slide.shapes.add_textbox.assert_called_once()
# 验证字体大小被设置
mock_tf = mock_slide.shapes.add_textbox.return_value.text_frame
assert len(mock_tf.paragraphs) == 1
_ = mock_tf.paragraphs[0].font.size
def _setup_mock_slide(self, num_paragraphs=1):
"""辅助函数:创建 mock slide
Args:
num_paragraphs: 要创建的段落数量,默认为 1
"""
mock_slide = Mock()
mock_text_frame = Mock()
mock_text_frame.word_wrap = True
mock_paragraph = Mock()
mock_paragraph.font = Mock()
mock_text_frame.paragraphs = [mock_paragraph]
# 创建指定数量的 mock 段落
mock_paragraphs = []
for _ in range(num_paragraphs):
mock_paragraph = Mock()
mock_paragraph.font = Mock()
mock_paragraphs.append(mock_paragraph)
mock_text_frame.paragraphs = mock_paragraphs
mock_textbox = Mock()
mock_textbox.text_frame = mock_text_frame