diff --git a/core/presentation.py b/core/presentation.py index c596421..1730f8a 100644 --- a/core/presentation.py +++ b/core/presentation.py @@ -29,8 +29,14 @@ class Presentation: validate_presentation_yaml(self.data, str(pres_file)) # 获取演示文稿尺寸 - metadata = self.data.get('metadata', {}) - self.size = metadata.get('size', '16:9') + metadata = self.data.get("metadata", {}) + self.size = metadata.get("size", "16:9") + + # 验证尺寸值 + if not isinstance(self.size, str): + raise ValueError( + f"无效的尺寸值: {self.size},尺寸必须是字符串(如 '16:9' 或 '4:3')" + ) # 模板缓存 self.template_cache = {} @@ -61,31 +67,28 @@ class Presentation: Returns: dict: 包含 background 和 elements 的字典 """ - if 'template' in slide_data: + if "template" in slide_data: # 使用模板 - template_name = slide_data['template'] + template_name = slide_data["template"] template = self.get_template(template_name) - vars_values = slide_data.get('vars', {}) + vars_values = slide_data.get("vars", {}) elements = template.render(vars_values) # 合并背景(如果有) - background = slide_data.get('background', None) + background = slide_data.get("background", None) # 将元素字典转换为元素对象 element_objects = [create_element(elem) for elem in elements] - return { - 'background': background, - 'elements': element_objects - } + return {"background": background, "elements": element_objects} else: # 自定义幻灯片 - elements = slide_data.get('elements', []) + elements = slide_data.get("elements", []) # 将元素字典转换为元素对象 element_objects = [create_element(elem) for elem in elements] return { - 'background': slide_data.get('background'), - 'elements': element_objects + "background": slide_data.get("background"), + "elements": element_objects, } diff --git a/renderers/html_renderer.py b/renderers/html_renderer.py index 2935aec..4f96712 100644 --- a/renderers/html_renderer.py +++ b/renderers/html_renderer.py @@ -30,12 +30,12 @@ class HtmlRenderer: elements_html = "" bg_style = "" - if slide_data.get('background'): - bg = slide_data['background'] - if 'color' in bg: + if slide_data.get("background"): + bg = slide_data["background"] + if "color" in bg: bg_style = f"background: {bg['color']};" - for elem in slide_data.get('elements', []): + for elem in slide_data.get("elements", []): try: if isinstance(elem, TextElement): elements_html += self.render_text(elem) @@ -46,7 +46,9 @@ class HtmlRenderer: elif isinstance(elem, ImageElement): elements_html += self.render_image(elem, base_path) except Exception as e: - elements_html += f'
渲染错误: {str(e)}
' + elements_html += ( + f'
渲染错误: {str(e)}
' + ) return f'''
@@ -70,18 +72,20 @@ class HtmlRenderer: top: {elem.box[1] * DPI}px; width: {elem.box[2] * DPI}px; height: {elem.box[3] * DPI}px; - font-size: {elem.font.get('size', 16)}pt; - color: {elem.font.get('color', '#000000')}; - text-align: {elem.font.get('align', 'left')}; - {'font-weight: bold;' if elem.font.get('bold') else ''} - {'font-style: italic;' if elem.font.get('italic') else ''} + font-size: {elem.font.get("size", 16)}pt; + color: {elem.font.get("color", "#000000")}; + text-align: {elem.font.get("align", "left")}; + {"font-weight: bold;" if elem.font.get("bold") else ""} + {"font-style: italic;" if elem.font.get("italic") else ""} display: flex; align-items: center; white-space: normal; overflow-wrap: break-word; """ - content = elem.content.replace('<', '<').replace('>', '>') + content = ( + elem.content.replace("&", "&").replace("<", "<").replace(">", ">") + ) return f'
{content}
' def render_shape(self, elem: ShapeElement): @@ -95,23 +99,23 @@ class HtmlRenderer: str: HTML 代码 """ border_radius = { - 'rectangle': '0', - 'ellipse': '50%', - 'rounded_rectangle': '8px' - }.get(elem.shape, '0') + "rectangle": "0", + "ellipse": "50%", + "rounded_rectangle": "8px", + }.get(elem.shape, "0") style = f""" left: {elem.box[0] * DPI}px; top: {elem.box[1] * DPI}px; width: {elem.box[2] * DPI}px; height: {elem.box[3] * DPI}px; - background: {elem.fill if elem.fill else 'transparent'}; + background: {elem.fill if elem.fill else "transparent"}; border-radius: {border_radius}; """ if elem.line: style += f""" - border: {elem.line.get('width', 1)}pt solid {elem.line.get('color', '#000000')}; + border: {elem.line.get("width", 1)}pt solid {elem.line.get("color", "#000000")}; """ return f'
' @@ -138,14 +142,14 @@ class HtmlRenderer: cell_style = f"font-size: {elem.style.get('font_size', 14)}pt;" if i == 0: - if 'header_bg' in elem.style: + if "header_bg" in elem.style: cell_style += f"background: {elem.style['header_bg']};" - if 'header_color' in elem.style: + if "header_color" in elem.style: cell_style += f"color: {elem.style['header_color']};" - cell_content = str(cell).replace('<', '<').replace('>', '>') + cell_content = str(cell).replace("<", "<").replace(">", ">") cells_html += f'{cell_content}' - rows_html += f'{cells_html}' + rows_html += f"{cells_html}" return f'{rows_html}
' diff --git a/tests/conftest_pptx.py b/tests/conftest_pptx.py index 29aefed..0047a1a 100644 --- a/tests/conftest_pptx.py +++ b/tests/conftest_pptx.py @@ -16,6 +16,7 @@ from pptx.enum.shapes import MSO_SHAPE class PptxValidationError: """验证错误信息""" + def __init__(self, level: str, message: str): self.level = level # 'ERROR' or 'WARNING' self.message = message @@ -69,13 +70,16 @@ class PptxFileValidator: actual = len(prs.slides) if actual != expected_count: self.errors.append( - PptxValidationError("ERROR", - f"幻灯片数量不匹配: 期望 {expected_count}, 实际 {actual}") + PptxValidationError( + "ERROR", f"幻灯片数量不匹配: 期望 {expected_count}, 实际 {actual}" + ) ) return False return True - def validate_slide_size(self, prs: Presentation, expected_size: str = "16:9") -> bool: + def validate_slide_size( + self, prs: Presentation, expected_size: str = "16:9" + ) -> bool: """验证幻灯片尺寸""" expected = self.SIZE_16_9 if expected_size == "16:9" else self.SIZE_4_3 actual_width = prs.slide_width.inches @@ -83,15 +87,19 @@ class PptxFileValidator: if abs(actual_width - expected[0]) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"幻灯片宽度不匹配: 期望 {expected[0]}, 实际 {actual_width}") + PptxValidationError( + "ERROR", + f"幻灯片宽度不匹配: 期望 {expected[0]}, 实际 {actual_width}", + ) ) return False if abs(actual_height - expected[1]) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"幻灯片高度不匹配: 期望 {expected[1]}, 实际 {actual_height}") + PptxValidationError( + "ERROR", + f"幻灯片高度不匹配: 期望 {expected[1]}, 实际 {actual_height}", + ) ) return False @@ -113,8 +121,11 @@ class PptxFileValidator: counts["text_box"] += 1 elif hasattr(shape, "image"): counts["picture"] += 1 - elif shape.shape_type in [MSO_SHAPE.RECTANGLE, MSO_SHAPE.OVAL, - MSO_SHAPE.ROUNDED_RECTANGLE]: + elif shape.shape_type in [ + MSO_SHAPE.RECTANGLE, + MSO_SHAPE.OVAL, + MSO_SHAPE.ROUNDED_RECTANGLE, + ]: counts["shape"] += 1 elif shape.has_table: counts["table"] += 1 @@ -125,10 +136,14 @@ class PptxFileValidator: return counts - def validate_text_element(self, slide, index: int = 0, - expected_content: Optional[str] = None, - expected_font_size: Optional[int] = None, - expected_color: Optional[tuple] = None) -> bool: + def validate_text_element( + self, + slide, + index: int = 0, + expected_content: Optional[str] = None, + expected_font_size: Optional[int] = None, + expected_color: Optional[tuple] = None, + ) -> bool: """ 验证文本元素 @@ -142,7 +157,8 @@ class PptxFileValidator: Returns: 验证是否通过 """ - text_boxes = [s for s in slide.shapes if s.shape_type == MSO_SHAPE.TEXT_BOX] + # 通过检查是否有text_frame属性来判断是否是文本框 + text_boxes = [s for s in slide.shapes if hasattr(s, "text_frame")] if index >= len(text_boxes): self.errors.append( @@ -158,8 +174,10 @@ class PptxFileValidator: actual_content = text_frame.text if actual_content != expected_content: self.errors.append( - PptxValidationError("ERROR", - f"文本内容不匹配: 期望 '{expected_content}', 实际 '{actual_content}'") + PptxValidationError( + "ERROR", + f"文本内容不匹配: 期望 '{expected_content}', 实际 '{actual_content}'", + ) ) return False @@ -168,8 +186,10 @@ class PptxFileValidator: actual_size = text_frame.paragraphs[0].font.size.pt if abs(actual_size - expected_font_size) > 1: self.errors.append( - PptxValidationError("ERROR", - f"字体大小不匹配: 期望 {expected_font_size}pt, 实际 {actual_size}pt") + PptxValidationError( + "ERROR", + f"字体大小不匹配: 期望 {expected_font_size}pt, 实际 {actual_size}pt", + ) ) return False @@ -180,20 +200,25 @@ class PptxFileValidator: actual_color = (actual_rgb[0], actual_rgb[1], actual_rgb[2]) if actual_color != expected_color: self.errors.append( - PptxValidationError("ERROR", - f"字体颜色不匹配: 期望 RGB{expected_color}, 实际 RGB{actual_color}") + PptxValidationError( + "ERROR", + f"字体颜色不匹配: 期望 RGB{expected_color}, 实际 RGB{actual_color}", + ) ) return False except Exception: - self.errors.append( - PptxValidationError("WARNING", "无法获取字体颜色") - ) + self.errors.append(PptxValidationError("WARNING", "无法获取字体颜色")) return True - def validate_position(self, shape, expected_left: float, expected_top: float, - expected_width: Optional[float] = None, - expected_height: Optional[float] = None) -> bool: + def validate_position( + self, + shape, + expected_left: float, + expected_top: float, + expected_width: Optional[float] = None, + expected_height: Optional[float] = None, + ) -> bool: """ 验证元素位置和尺寸 @@ -212,15 +237,17 @@ class PptxFileValidator: if abs(actual_left - expected_left) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"左边距不匹配: 期望 {expected_left}, 实际 {actual_left}") + PptxValidationError( + "ERROR", f"左边距不匹配: 期望 {expected_left}, 实际 {actual_left}" + ) ) return False if abs(actual_top - expected_top) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"上边距不匹配: 期望 {expected_top}, 实际 {actual_top}") + PptxValidationError( + "ERROR", f"上边距不匹配: 期望 {expected_top}, 实际 {actual_top}" + ) ) return False @@ -228,8 +255,10 @@ class PptxFileValidator: actual_width = shape.width.inches if abs(actual_width - expected_width) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"宽度不匹配: 期望 {expected_width}, 实际 {actual_width}") + PptxValidationError( + "ERROR", + f"宽度不匹配: 期望 {expected_width}, 实际 {actual_width}", + ) ) return False @@ -237,8 +266,10 @@ class PptxFileValidator: actual_height = shape.height.inches if abs(actual_height - expected_height) > self.TOLERANCE: self.errors.append( - PptxValidationError("ERROR", - f"高度不匹配: 期望 {expected_height}, 实际 {actual_height}") + PptxValidationError( + "ERROR", + f"高度不匹配: 期望 {expected_height}, 实际 {actual_height}", + ) ) return False @@ -265,23 +296,21 @@ class PptxFileValidator: ) if actual_rgb != expected_rgb: self.errors.append( - PptxValidationError("ERROR", - f"背景颜色不匹配: 期望 RGB{expected_rgb}, 实际 RGB{actual_rgb}") + PptxValidationError( + "ERROR", + f"背景颜色不匹配: 期望 RGB{expected_rgb}, 实际 RGB{actual_rgb}", + ) ) return False except Exception as e: - self.errors.append( - PptxValidationError("WARNING", f"无法获取背景颜色: {e}") - ) + self.errors.append(PptxValidationError("WARNING", f"无法获取背景颜色: {e}")) return True def _validate_file_exists(self, pptx_path: Path) -> bool: """验证文件存在且大小大于 0""" if not pptx_path.exists(): - self.errors.append( - PptxValidationError("ERROR", f"文件不存在: {pptx_path}") - ) + self.errors.append(PptxValidationError("ERROR", f"文件不存在: {pptx_path}")) return False if pptx_path.stat().st_size == 0: diff --git a/tests/e2e/test_check_cmd.py b/tests/e2e/test_check_cmd.py index 33ad8e0..61dd3b2 100644 --- a/tests/e2e/test_check_cmd.py +++ b/tests/e2e/test_check_cmd.py @@ -15,13 +15,10 @@ class TestCheckCmd: def run_check(self, *args): """辅助函数:运行 check 命令""" - cmd = [sys.executable, "-m", "uv", "run", "python", "yaml2pptx.py", "check"] + cmd = ["uv", "run", "python", "yaml2pptx.py", "check"] cmd.extend(args) result = subprocess.run( - cmd, - capture_output=True, - text=True, - cwd=Path(__file__).parent.parent.parent + cmd, capture_output=True, text=True, cwd=Path(__file__).parent.parent.parent ) return result diff --git a/tests/e2e/test_convert_cmd.py b/tests/e2e/test_convert_cmd.py index 608ebea..dbedef0 100644 --- a/tests/e2e/test_convert_cmd.py +++ b/tests/e2e/test_convert_cmd.py @@ -16,13 +16,10 @@ class TestConvertCmd: def run_convert(self, *args): """辅助函数:运行 convert 命令""" - cmd = [sys.executable, "-m", "uv", "run", "python", "yaml2pptx.py", "convert"] + cmd = ["uv", "run", "python", "yaml2pptx.py", "convert"] cmd.extend(args) result = subprocess.run( - cmd, - capture_output=True, - text=True, - cwd=Path(__file__).parent.parent.parent + cmd, capture_output=True, text=True, cwd=Path(__file__).parent.parent.parent ) return result @@ -37,19 +34,29 @@ class TestConvertCmd: def test_auto_output_filename(self, sample_yaml, temp_dir): """测试自动生成输出文件名""" - # 在 temp_dir 中运行 + # sample_yaml 位于 temp_dir 中,转换时输出也会在 temp_dir + # 但因为 cwd 是项目根目录,所以输出文件的路径需要计算 + # 实际上,由于 sample_yaml 使用 tempfile,输出会在 temp_dir 中 result = subprocess.run( - [sys.executable, "-m", "uv", "run", "python", - "yaml2pptx.py", "convert", str(sample_yaml)], + [ + "uv", + "run", + "python", + "yaml2pptx.py", + "convert", + str(sample_yaml), + ], capture_output=True, text=True, - cwd=temp_dir + cwd=Path(__file__).parent.parent.parent, ) - assert result.returncode == 0 - # 应该生成与输入同名的 .pptx 文件 + assert result.returncode == 0, f"Command failed: {result.stderr}" + # 应该生成与输入同名的 .pptx 文件(在 temp_dir 中) expected_output = temp_dir / "test.pptx" - assert expected_output.exists() + assert expected_output.exists(), ( + f"Expected {expected_output} to exist, but didn't" + ) def test_conversion_with_template(self, temp_dir, sample_template): """测试使用模板转换""" @@ -67,9 +74,7 @@ slides: output = temp_dir / "output.pptx" result = self.run_convert( - str(yaml_path), - str(output), - "--template-dir", str(sample_template) + str(yaml_path), str(output), "--template-dir", str(sample_template) ) assert result.returncode == 0 @@ -78,11 +83,7 @@ slides: def test_skip_validation(self, sample_yaml, temp_dir): """测试跳过验证""" output = temp_dir / "output.pptx" - result = self.run_convert( - str(sample_yaml), - str(output), - "--skip-validation" - ) + result = self.run_convert(str(sample_yaml), str(output), "--skip-validation") assert result.returncode == 0 assert output.exists() @@ -95,11 +96,7 @@ slides: output.write_text("existing") # 使用 --force 应该覆盖 - result = self.run_convert( - str(sample_yaml), - str(output), - "--force" - ) + result = self.run_convert(str(sample_yaml), str(output), "--force") assert result.returncode == 0 # 文件应该是有效的 PPTX,不是原来的文本 @@ -129,7 +126,12 @@ slides: def test_conversion_with_all_element_types(self, temp_dir, sample_image): """测试转换包含所有元素类型的 YAML""" - fixtures_yaml = Path(__file__).parent.parent / "fixtures" / "yaml_samples" / "full_features.yaml" + fixtures_yaml = ( + Path(__file__).parent.parent + / "fixtures" + / "yaml_samples" + / "full_features.yaml" + ) if not fixtures_yaml.exists(): pytest.skip("full_features.yaml not found") @@ -158,7 +160,7 @@ slides: size: 24 """ yaml_path = temp_dir / "test.yaml" - yaml_path.write_text(yaml_content, encoding='utf-8') + yaml_path.write_text(yaml_content, encoding="utf-8") output = temp_dir / "output.pptx" result = self.run_convert(str(yaml_path), str(output)) @@ -174,9 +176,9 @@ slides: def test_different_slide_sizes(self, temp_dir): """测试不同的幻灯片尺寸""" for size in ["16:9", "4:3"]: - yaml_content = f""" + yaml_content = f''' metadata: - size: {size} + size: "{size}" slides: - elements: @@ -185,7 +187,7 @@ slides: content: "Size {size}" font: size: 24 -""" +''' yaml_path = temp_dir / f"test_{size.replace(':', '')}.yaml" yaml_path.write_text(yaml_content) diff --git a/tests/fixtures/images/test_image.png b/tests/fixtures/images/test_image.png new file mode 100644 index 0000000..e4d133f Binary files /dev/null and b/tests/fixtures/images/test_image.png differ diff --git a/tests/fixtures/yaml_samples/full_features.yaml b/tests/fixtures/yaml_samples/full_features.yaml index 7595b9d..ae7de01 100644 --- a/tests/fixtures/yaml_samples/full_features.yaml +++ b/tests/fixtures/yaml_samples/full_features.yaml @@ -28,7 +28,7 @@ slides: - elements: - type: image box: [1, 1, 4, 3] - src: "test_image.png" + src: "../images/test_image.png" # 形状元素幻灯片 - elements: diff --git a/tests/integration/test_presentation.py b/tests/integration/test_presentation.py index fb5d668..86110b3 100644 --- a/tests/integration/test_presentation.py +++ b/tests/integration/test_presentation.py @@ -21,7 +21,7 @@ class TestPresentationInit: def test_init_with_template_dir(self, sample_yaml, sample_template): """测试带模板目录初始化""" pres = Presentation(str(sample_yaml), str(sample_template)) - assert pres.template_dir == sample_template + assert pres.templates_dir == str(sample_template) class TestTemplateCaching: @@ -86,7 +86,11 @@ slides: # 模板变量应该被替换 elements = rendered["elements"] - title_elem = next(e for e in elements if e.get("type") == "text" and "Test Title" in e.get("content", "")) + title_elem = next( + e + for e in elements + if e.get("type") == "text" and "Test Title" in e.get("content", "") + ) assert title_elem is not None def test_render_slide_with_conditional_element(self, temp_dir, sample_template): diff --git a/tests/unit/test_renderers/test_html_renderer.py b/tests/unit/test_renderers/test_html_renderer.py index d01a915..5eeef6d 100644 --- a/tests/unit/test_renderers/test_html_renderer.py +++ b/tests/unit/test_renderers/test_html_renderer.py @@ -32,7 +32,7 @@ class TestRenderText: elem = TextElement( content="Test Content", box=[1, 2, 3, 0.5], - font={"size": 18, "color": "#333333"} + font={"size": 18, "color": "#333333"}, ) html = renderer.render_text(elem) @@ -48,9 +48,7 @@ class TestRenderText: """测试渲染粗体文本""" renderer = HtmlRenderer() elem = TextElement( - content="Bold Text", - box=[0, 0, 1, 1], - font={"size": 16, "bold": True} + content="Bold Text", box=[0, 0, 1, 1], font={"size": 16, "bold": True} ) html = renderer.render_text(elem) @@ -61,9 +59,7 @@ class TestRenderText: """测试渲染斜体文本""" renderer = HtmlRenderer() elem = TextElement( - content="Italic Text", - box=[0, 0, 1, 1], - font={"size": 16, "italic": True} + content="Italic Text", box=[0, 0, 1, 1], font={"size": 16, "italic": True} ) html = renderer.render_text(elem) @@ -74,9 +70,7 @@ class TestRenderText: """测试渲染居中对齐文本""" renderer = HtmlRenderer() elem = TextElement( - content="Centered", - box=[0, 0, 1, 1], - font={"align": "center"} + content="Centered", box=[0, 0, 1, 1], font={"align": "center"} ) html = renderer.render_text(elem) @@ -87,9 +81,7 @@ class TestRenderText: """测试渲染右对齐文本""" renderer = HtmlRenderer() elem = TextElement( - content="Right Aligned", - box=[0, 0, 1, 1], - font={"align": "right"} + content="Right Aligned", box=[0, 0, 1, 1], font={"align": "right"} ) html = renderer.render_text(elem) @@ -99,11 +91,7 @@ class TestRenderText: def test_render_text_with_default_align(self): """测试默认左对齐""" renderer = HtmlRenderer() - elem = TextElement( - content="Default", - box=[0, 0, 1, 1], - font={} - ) + elem = TextElement(content="Default", box=[0, 0, 1, 1], font={}) html = renderer.render_text(elem) @@ -113,9 +101,7 @@ class TestRenderText: """测试 HTML 特殊字符转义""" renderer = HtmlRenderer() elem = TextElement( - content="", - box=[0, 0, 1, 1], - font={} + content="", box=[0, 0, 1, 1], font={} ) html = renderer.render_text(elem) @@ -127,11 +113,7 @@ class TestRenderText: def test_render_text_with_special_characters(self): """测试特殊字符处理""" renderer = HtmlRenderer() - elem = TextElement( - content="Test & < > \" '", - box=[0, 0, 1, 1], - font={} - ) + elem = TextElement(content="Test & < > \" '", box=[0, 0, 1, 1], font={}) html = renderer.render_text(elem) @@ -143,24 +125,18 @@ class TestRenderText: """测试长文本内容""" renderer = HtmlRenderer() long_content = "A" * 500 - elem = TextElement( - content=long_content, - box=[0, 0, 5, 1], - font={"size": 12} - ) + elem = TextElement(content=long_content, box=[0, 0, 5, 1], font={"size": 12}) html = renderer.render_text(elem) assert long_content in html - assert "word-wrap: break-word" in html + assert "overflow-wrap: break-word" in html def test_render_text_with_newlines(self): """测试包含换行符的文本""" renderer = HtmlRenderer() elem = TextElement( - content="Line 1\nLine 2\nLine 3", - box=[0, 0, 5, 2], - font={"size": 14} + content="Line 1\nLine 2\nLine 3", box=[0, 0, 5, 2], font={"size": 14} ) html = renderer.render_text(elem) @@ -172,11 +148,7 @@ class TestRenderText: def test_render_text_with_unicode(self): """测试 Unicode 字符""" renderer = HtmlRenderer() - elem = TextElement( - content="测试中文 🌍", - box=[0, 0, 5, 1], - font={"size": 16} - ) + elem = TextElement(content="测试中文 🌍", box=[0, 0, 5, 1], font={"size": 16}) html = renderer.render_text(elem) @@ -186,11 +158,7 @@ class TestRenderText: def test_render_text_with_empty_font(self): """测试空字体属性""" renderer = HtmlRenderer() - elem = TextElement( - content="Test", - box=[0, 0, 1, 1], - font={} - ) + elem = TextElement(content="Test", box=[0, 0, 1, 1], font={}) html = renderer.render_text(elem) @@ -205,11 +173,7 @@ class TestRenderShape: def test_render_rectangle(self): """测试渲染矩形""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1, 1, 2, 1], - shape="rectangle", - fill="#4a90e2" - ) + elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2") html = renderer.render_shape(elem) @@ -220,11 +184,7 @@ class TestRenderShape: def test_render_ellipse(self): """测试渲染椭圆""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1, 1, 2, 2], - shape="ellipse", - fill="#e24a4a" - ) + elem = ShapeElement(box=[1, 1, 2, 2], shape="ellipse", fill="#e24a4a") html = renderer.render_shape(elem) @@ -234,11 +194,7 @@ class TestRenderShape: def test_render_rounded_rectangle(self): """测试渲染圆角矩形""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1, 1, 2, 1], - shape="rounded_rectangle", - fill="#4ae290" - ) + elem = ShapeElement(box=[1, 1, 2, 1], shape="rounded_rectangle", fill="#4ae290") html = renderer.render_shape(elem) @@ -248,11 +204,7 @@ class TestRenderShape: def test_render_shape_without_fill(self): """测试无填充颜色的形状""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1, 1, 2, 1], - shape="rectangle", - fill=None - ) + elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill=None) html = renderer.render_shape(elem) @@ -265,7 +217,7 @@ class TestRenderShape: box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2", - line={"color": "#000000", "width": 2} + line={"color": "#000000", "width": 2}, ) html = renderer.render_shape(elem) @@ -279,7 +231,7 @@ class TestRenderShape: box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2", - line={"color": "#000000"} + line={"color": "#000000"}, ) html = renderer.render_shape(elem) @@ -289,11 +241,7 @@ class TestRenderShape: def test_render_shape_without_line(self): """测试无边框的形状""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1, 1, 2, 1], - shape="rectangle", - fill="#4a90e2" - ) + elem = ShapeElement(box=[1, 1, 2, 1], shape="rectangle", fill="#4a90e2") html = renderer.render_shape(elem) @@ -302,22 +250,18 @@ class TestRenderShape: def test_render_shape_position(self): """测试形状位置计算""" renderer = HtmlRenderer() - elem = ShapeElement( - box=[1.5, 2.5, 3, 1.5], - shape="rectangle", - fill="#000000" - ) + elem = ShapeElement(box=[1.5, 2.5, 3, 1.5], shape="rectangle", fill="#000000") html = renderer.render_shape(elem) # 1.5 * 96 = 144 - assert "left: 144px" in html + assert "left: 144" in html # 2.5 * 96 = 240 - assert "top: 240px" in html + assert "top: 240" in html # 3 * 96 = 288 - assert "width: 288px" in html + assert "width: 288" in html # 1.5 * 96 = 144 - assert "height: 144px" in html + assert "height: 144" in html class TestRenderTable: @@ -330,7 +274,7 @@ class TestRenderTable: position=[1, 1], col_widths=[2, 2, 2], data=[["A", "B", "C"], ["1", "2", "3"]], - style={} + style={}, ) html = renderer.render_table(elem) @@ -350,11 +294,7 @@ class TestRenderTable: position=[1, 1], col_widths=[2, 2], data=[["H1", "H2"], ["D1", "D2"]], - style={ - "font_size": 14, - "header_bg": "#4a90e2", - "header_color": "#ffffff" - } + style={"font_size": 14, "header_bg": "#4a90e2", "header_color": "#ffffff"}, ) html = renderer.render_table(elem) @@ -367,10 +307,7 @@ class TestRenderTable: """测试表格位置""" renderer = HtmlRenderer() elem = TableElement( - position=[2, 3], - col_widths=[1, 1], - data=[["A", "B"]], - style={} + position=[2, 3], col_widths=[1, 1], data=[["A", "B"]], style={} ) html = renderer.render_table(elem) @@ -383,12 +320,7 @@ class TestRenderTable: def test_render_table_with_default_font_size(self): """测试默认字体大小""" renderer = HtmlRenderer() - elem = TableElement( - position=[0, 0], - col_widths=[1], - data=[["Cell"]], - style={} - ) + elem = TableElement(position=[0, 0], col_widths=[1], data=[["Cell"]], style={}) html = renderer.render_table(elem) @@ -398,10 +330,7 @@ class TestRenderTable: """测试表格内容转义""" renderer = HtmlRenderer() elem = TableElement( - position=[0, 0], - col_widths=[1], - data=[["