1
0
Files
PPTX/core/elements.py
lanyuanxiaoyao f34405be36 feat: 移除图片适配模式功能
移除图片 fit 和 background 参数支持,简化图片渲染逻辑。系统恢复到直接使用 python-pptx 原生图片添加功能,图片将被拉伸到指定尺寸。

变更内容:
- 移除 ImageElement 的 fit 和 background 字段
- 移除 metadata.dpi 配置
- 删除 utils/image_utils.py 图片处理工具模块
- 删除 validators/image_config.py 验证器
- 简化 PPTX 和 HTML 渲染器的图片处理逻辑
- HTML 渲染器使用硬编码 DPI=96(Web 标准)
- 删除相关测试文件(单元测试、集成测试、e2e 测试)
- 更新规格文档和用户文档
- 保留 Pillow 依赖用于未来可能的图片处理需求

影响:
- 删除 11 个文件
- 修改 10 个文件
- 净减少 1558 行代码
- 所有 402 个测试通过

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-04 14:23:12 +08:00

220 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
元素抽象层模块
定义统一的元素数据类,支持元素验证和未来扩展。
"""
from dataclasses import dataclass, field
from typing import Optional, List
import re
def _is_valid_color(color: str) -> bool:
"""
验证颜色格式是否正确
Args:
color: 颜色字符串
Returns:
是否为有效的颜色格式(#RGB 或 #RRGGBB
"""
pattern = r'^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$'
return bool(re.match(pattern, color))
@dataclass
class TextElement:
"""文本元素"""
type: str = 'text'
content: str = ''
box: list = field(default_factory=lambda: [1, 1, 8, 1])
font: dict = field(default_factory=dict)
def __post_init__(self):
"""创建时验证"""
if not isinstance(self.box, list) or len(self.box) != 4:
raise ValueError("box 必须是包含 4 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查颜色格式
if self.font.get('color'):
if not _is_valid_color(self.font['color']):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的颜色格式: {self.font['color']} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
# 检查字体大小
if self.font.get('size'):
size = self.font['size']
if size < 8:
issues.append(ValidationIssue(
level="WARNING",
message=f"字体太小: {size}pt (建议 >= 8pt)",
location="",
code="FONT_TOO_SMALL"
))
elif size > 100:
issues.append(ValidationIssue(
level="WARNING",
message=f"字体太大: {size}pt (建议 <= 100pt)",
location="",
code="FONT_TOO_LARGE"
))
return issues
@dataclass
class ImageElement:
"""图片元素"""
type: str = 'image'
src: str = ''
box: list = field(default_factory=lambda: [1, 1, 4, 3])
def __post_init__(self):
"""创建时验证"""
if not self.src:
raise ValueError("图片元素必须指定 src")
if not self.box:
raise ValueError("图片元素必须指定 box")
if not isinstance(self.box, list) or len(self.box) != 4:
raise ValueError("box 必须是包含 4 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
return []
@dataclass
class ShapeElement:
"""形状元素"""
type: str = 'shape'
shape: str = 'rectangle'
box: list = field(default_factory=lambda: [1, 1, 2, 1])
fill: Optional[str] = None
line: Optional[dict] = None
def __post_init__(self):
"""创建时验证"""
if not isinstance(self.box, list) or len(self.box) != 4:
raise ValueError("box 必须是包含 4 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查形状类型枚举
valid_shapes = ['rectangle', 'ellipse', 'rounded_rectangle']
if self.shape not in valid_shapes:
issues.append(ValidationIssue(
level="ERROR",
message=f"不支持的形状类型: {self.shape} (支持: {', '.join(valid_shapes)})",
location="",
code="INVALID_SHAPE_TYPE"
))
# 检查填充颜色格式
if self.fill and not _is_valid_color(self.fill):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的填充颜色格式: {self.fill} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
# 检查线条颜色格式
if self.line and self.line.get('color'):
if not _is_valid_color(self.line['color']):
issues.append(ValidationIssue(
level="ERROR",
message=f"无效的线条颜色格式: {self.line['color']} (应为 #RRGGBB)",
location="",
code="INVALID_COLOR_FORMAT"
))
return issues
@dataclass
class TableElement:
"""表格元素"""
type: str = 'table'
data: list = field(default_factory=list)
position: list = field(default_factory=lambda: [1, 1])
col_widths: list = field(default_factory=list)
style: dict = field(default_factory=dict)
def __post_init__(self):
"""创建时验证"""
if not self.data:
raise ValueError("表格数据不能为空")
if not isinstance(self.position, list) or len(self.position) != 2:
raise ValueError("position 必须是包含 2 个数字的列表")
def validate(self) -> List:
"""验证元素自身的完整性"""
from validators.result import ValidationIssue
issues = []
# 检查表格数据行列数一致性
if self.data:
first_row_cols = len(self.data[0]) if isinstance(self.data[0], list) else 0
for i, row in enumerate(self.data[1:], start=1):
if isinstance(row, list):
if len(row) != first_row_cols:
issues.append(ValidationIssue(
level="ERROR",
message=f"表格数据行列数不一致: 第 {i+1} 行有 {len(row)} 列,期望 {first_row_cols}",
location="",
code="TABLE_INCONSISTENT_COLUMNS"
))
# 检查 col_widths 与列数是否匹配
if self.col_widths and self.data:
first_row_cols = len(self.data[0]) if isinstance(self.data[0], list) else 0
if len(self.col_widths) != first_row_cols:
issues.append(ValidationIssue(
level="WARNING",
message=f"col_widths 数量 ({len(self.col_widths)}) 与表格列数 ({first_row_cols}) 不匹配",
location="",
code="TABLE_COL_WIDTHS_MISMATCH"
))
return issues
def create_element(elem_dict: dict):
"""
元素工厂函数,从字典创建对应类型的元素对象
Args:
elem_dict: 元素字典,必须包含 type 字段
Returns:
对应类型的元素对象
Raises:
ValueError: 不支持的元素类型
"""
elem_type = elem_dict.get('type')
if elem_type == 'text':
return TextElement(**elem_dict)
elif elem_type == 'image':
return ImageElement(**elem_dict)
elif elem_type == 'shape':
return ShapeElement(**elem_dict)
elif elem_type == 'table':
return TableElement(**elem_dict)
else:
raise ValueError(f"不支持的元素类型: {elem_type}")