Install
openclaw skills install @tuobadaidai/ppt-style-matcheropenclaw skills install @tuobadaidai/ppt-style-matcher核心思路:分析原PPT风格 → 锁定配色/字体/布局参数 → 用python-pptx生成新页面 → 完全重建sldIdLst → P0-P3分级验证
⚠️ v4.1关键改进:色板提取用全局颜色频率(不再依赖采样页偏差)、智能采样增加颜色多样性、深色PPT文字色修正
| # | 规则 | 说明 |
|---|---|---|
| R1 | 颜色锁定 | 只允许使用从原PPT提取的5色+2文字色,禁止编造hex |
| R2 | 字体锁定 | 三级分工(标题/正文/注释),禁止换字体 |
| R3 | 布局计算先行 | 禁止凭感觉填坐标,所有位置必须从公式计算 |
| R4 | 只用形状+文本 | 禁止外部图片、禁止pptxgenjs(它会丢中文和关系) |
| R5 | 间距保护 | 分栏间距0.6-1.0cm,内容距底≥0.15cm |
| R6 | 每页≥20个shape | 低于此数为简单文本页,需重设计 |
| R7 | 验证读回 | 生成完毕必须用python-pptx读回+validate_layout()检查 |
在动手之前,先回答6个问题。不回答完不开始。
运行 scripts/analyze_style.py 提取风格参数:
python3 scripts/analyze_style.py <ppt路径> [--verbose]
必须确认以下参数后再继续(防幻觉):
脚本自动提取,输出格式:
COLOR_PRIMARY: #22C55E ← 主色,最高频非白非黑填充色
COLOR_SECONDARY: #374151 ← 辅色,中性色优先(深灰/深蓝/深绿)
COLOR_ACCENT: #C00000 ← 强调色,高饱和色优先(红/橙/蓝)
COLOR_BG: #FFFFFF ← 背景色
COLOR_TEXT: #1F2937 ← 主文字色(浅色PPT选深色,深色PPT选浅色)
COLOR_TEXT_LIGHT: #4B5563 ← 辅文字色
IS_DARK_THEME: False ← 是否深色主题
⚠️ v4改进:
按字号范围推断:
⚠️ 如果三个级别是同一字体(常见于中文PPT),标题用加粗、正文用常规即可。
从 --verbose 输出中提取:
MARGIN_L / MARGIN_R:左右安全边距HEADER_H:标题区高度FOOTER_H:底部装饰条高度CONTENT_X/Y/W/H:内容区范围has_left_right_split:是否左右分栏及比例is_simple_page:是否简单页(shape<10,布局参数无参考价值)⚠️ v4改进:简单页(shape<10)的布局参数自动清空,避免误导。参考页推荐在分析结果末尾输出。
脚本自动检测节奏,根据PPT类型切换策略:
⚠️ v4改进:浅色PPT不再报"连续37页浅色=视觉疲劳",改用结构节奏检测。
所有坐标必须从公式算出,禁止硬编码。
SLIDE_W = 33.87 # cm, 从分析结果
SLIDE_H = 19.05 # cm
MARGIN_L = 2.1
MARGIN_R = 1.8
HEADER_H = 3.5 # 标题区
FOOTER_H = 3.0 # 底部装饰条(如有)
CONTENT_X = MARGIN_L
CONTENT_Y = HEADER_H + 0.5 # 标题下方0.5cm
CONTENT_W = SLIDE_W - MARGIN_L - MARGIN_R
CONTENT_H = SLIDE_H - CONTENT_Y - FOOTER_H - 0.3
GAP = 0.8 # 分栏间距,0.6-1.0cm范围
# 根据内容量选择比例
# 文字密集 → 0.58 | 适中 → 0.6 | 精简 → 0.5
ratio_left = 0.6
LEFT_W = (CONTENT_W - GAP) * ratio_left
RIGHT_W = (CONTENT_W - GAP) * (1 - ratio_left)
LEFT_X = CONTENT_X
RIGHT_X = CONTENT_X + LEFT_W + GAP
# ⚠️ 验证:LEFT_X + LEFT_W + GAP + RIGHT_W 应 ≈ CONTENT_X + CONTENT_W
COL3_GAP = 0.6
COL3_W = (CONTENT_W - 2 * COL3_GAP) / 3
COL3_X = [CONTENT_X + i * (COL3_W + COL3_GAP) for i in range(3)]
# 左装饰条
DECOR_W = 0.2 # cm
decor_x = card_x
decor_w = DECOR_W
decor_h = card_h
# 标题
title_x = card_x + DECOR_W + 0.2
title_w = card_w - DECOR_W - 0.4
title_pt = 14 # 三级标题
# 描述
desc_x = title_x
desc_w = title_w
desc_pt = 12 # 注释字号
参考第二步的节奏感检测结果:
from pptx import Presentation
from pptx.util import Cm, Pt, Emu
from pptx.enum.text import PP_PARAGRAPH_ALIGNMENT
from pptx.dml.color import RGBColor
# ⚠️ PP_PARAGRAPH_ALIGNMENT 用法:
# PP_PARAGRAPH_ALIGNMENT.LEFT 左对齐
# PP_PARAGRAPH_ALIGNMENT.CENTER 居中
# PP_PARAGRAPH_ALIGNMENT.RIGHT 右对齐
# PP_PARAGRAPH_ALIGNMENT.JUSTIFY 两端对齐
# 不要用 pp.ALIGN_LEFT 等旧写法!
def add_shape(slide, left, top, width, height, fill_color=None):
"""添加矩形shape"""
shape = slide.shapes.add_shape(
1, # MSO_SHAPE.RECTANGLE
Cm(left), Cm(top), Cm(width), Cm(height)
)
shape.line.fill.background() # 无边框
if fill_color:
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor.from_string(fill_color)
return shape
def add_textbox(slide, left, top, width, height, text, font_name='Noto Sans SC',
font_size=14, bold=False, color='1F2937', align='left'):
"""添加文本框"""
txBox = slide.shapes.add_textbox(
Cm(left), Cm(top), Cm(width), Cm(height)
)
tf = txBox.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = text
# 对齐方式
align_map = {
'left': PP_PARAGRAPH_ALIGNMENT.LEFT,
'center': PP_PARAGRAPH_ALIGNMENT.CENTER,
'right': PP_PARAGRAPH_ALIGNMENT.RIGHT,
'justify': PP_PARAGRAPH_ALIGNMENT.JUSTIFY,
}
p.alignment = align_map.get(align, PP_PARAGRAPH_ALIGNMENT.LEFT)
run = p.runs[0]
run.font.name = font_name
run.font.size = Pt(font_size)
run.font.bold = bold
run.font.color.rgb = RGBColor.from_string(color)
# ⚠️ 设置东亚字体(解决中文渲染问题)
set_ea_font(run, font_name)
return txBox
from scripts.analyze_style import set_ea_font, set_line_spacing, clean_shape
# 每个文字run都必须设置东亚字体
run.font.name = "Noto Sans SC" # 西文字体
set_ea_font(run, "Noto Sans SC") # 东亚字体(必做!)
# 显式设置行距
p = tf.paragraphs[0]
set_line_spacing(p, font_size_pt=14, ratio=1.35)
# 创建shape后清理自动样式
shape = add_shape(...)
clean_shape(shape)
⚠️ 踩坑记录:PP_PARAGRAPH_ALIGNMENT 是枚举类,不是模块常量。正确写法是 PP_PARAGRAPH_ALIGNMENT.LEFT,不是 PP_PARAGRAPH_ALIGNMENT.LEFT 从 pptx.enum 直接导入。
python-pptx 的 slide 顺序由 presentation.xml 中的 <p:sldIdLst> 控制。
不能用 addprevious/addnext 连续操作(索引会变导致顺序错乱)。
唯一可靠方法:清空sldIdLst → 按新顺序append。
from lxml import etree
# ✅ 正确的API(python-pptx没有 prs.presentation.sldIdLst 属性!)
ns = '{http://schemas.openxmlformats.org/presentationml/2006/main}'
sldIdLst = prs.part._element.find(f'{ns}sldIdLst')
# 1. 创建新slide(python-pptx会自动追加到末尾)
for i in range(4): # 4页新slide
prs.slides.add_slide(prs.slide_layouts[6])
# 2. 记录当前sldIdLst的所有元素
all_ids = list(sldIdLst)
# 3. 确定新的页面顺序
# 假设:原PPT有63页,新4页在末尾(-4),要替换第2-5页(index 1-4)
new_ids = all_ids[-4:] # 新页面
keep_ids = [all_ids[0]] + all_ids[5:-4] # 第1页 + 第6-63页
desired_order = keep_ids[:1] + new_ids + keep_ids[1:]
# 结果:[原1, 新1, 新2, 新3, 新4, 原6, 原7, ..., 原63]
# 4. 完全重建sldIdLst(⚠️ 不要用addprevious/addnext!)
for sid in list(sldIdLst):
sldIdLst.remove(sid)
for sid in desired_order:
sldIdLst.append(sid)
# 5. 保存
prs.save(output_path)
⚠️ 常见错误:
prs.presentation.sldIdLst → API不存在,必须用 prs.part._element.find()sldIdLst.insert(idx, elem) → lxml虽然支持,但连续insert索引会变扫描所有页文字,替换模板残留关键词:
RESIDUAL_KEYWORDS = ['OPPO', 'Tony', 'Alen', '逍遥子', '逍遥子班']
REPLACEMENTS = {
'OPPO': '国药太极',
'Tony': '导师A',
'Alen': '导师B',
'逍遥子班': '领航班',
'逍遥子': '领航',
}
for slide in prs.slides:
for shape in slide.shapes:
if shape.has_text_frame:
for para in shape.text_frame.paragraphs:
for run in para.runs:
for old, new in REPLACEMENTS.items():
if old in run.text:
run.text = run.text.replace(old, new)
from scripts.analyze_style import validate_layout
result = validate_layout(
prs,
slide_indices=[1, 2, 3, 4], # 新增/修改的页索引(0-based)
palette=palette, # 5色色板
residual_keywords=['OPPO', 'Tony', 'Alen'], # 残留关键词
)
print(f"P0通过: {result['passed']}")
for level in ['P0', 'P1', 'P2', 'P3']:
if result[level]:
print(f"\n{level}问题:")
for issue in result[level]:
print(f" - {issue}")
| 级别 | 类型 | 检查项 | 不通过后果 |
|---|---|---|---|
| P0 | 阻断级 | 越界/图片shape/shape<20/残留关键词 | 不交付 |
| P1 | 功能级 | 颜色一致性/底部留白/文字溢出 | 需修复 |
| P2 | 品质级 | 间距均匀/对齐/标题统一 | 建议修复 |
| P3 | 润色级 | 节奏感/信息密度/装饰元素 | 可选 |
| 问题 | 修复 | 影响 |
|---|---|---|
| 智能采样只看shape数量,选出的页颜色偏红 | 新增颜色多样性加分+贪心算法确保颜色覆盖 | 色板无偏差 |
| 色板提取只用采样页频率(采样偏差) | PRIMARY等核心角色改用全局颜色频率 | 色板准确 |
| validate_color_usage缩进bug(只检查最后一个slide) | for循环体修正缩进 | P1颜色验证生效 |
| SKILL.md sldIdLst API过时 | 改为完全重建法 + prs.part._element.find() | 代码可运行 |
| 深色PPT TEXT色被识别为强调色 | 检测深色主题后TEXT优先选浅色 | 色板正确 |
| SECONDARY/ACCENT角色倒挂 | SECONDARY优先中性色,ACCENT优先高饱和色 | 角色语义正确 |
| 默认采样4页中3页是简单页 | 智能采样:优先shape≥20的设计良好页+颜色多样性 | 色板质量提升 |
| 简单页布局参数误导 | shape<10标记为简单页,清空布局参数 | 避免误导 |
| 浅色PPT报"连续37页浅色=视觉疲劳" | 浅色PPT切换为结构节奏检测 | 不误报 |
| 缺少参考页推荐 | analyze_ppt自动输出参考页 | 免手动选择 |
| validate_layout坐标计算bug | 改为Emu(left + width) | 越界检测准确 |
| design-patterns.md缺sldIdLst操作指南 | 新增第十节:清空+按序重建法 | 最少踩坑 |
| 问题 | 修复 | 影响 |
|---|---|---|
| 全屏背景色块被误判为"顶部装饰条" | 新增 _is_fullscreen_shape() 过滤 | 布局参数准确 |
| 节奏感警告重复 | 滑动窗口找最长连续区间 | 输出简洁 |
| 5色色板PRIMARY和TEXT重复 | 区分填充色和文字色角色 | 色板可用 |
| ACCENT提取到极浅色 | bg_colors排除 + 亮度>240跳过 | 强调色有意义 |
| 字体三级分工只有1个字体 | 按字号范围推断三级 | 信息完整 |
| 底部装饰条全部0.0 | 降低检测阈值 | 可检测到 |
| validate_layout不存在 | 新增完整函数 | 可用 |
| PP_PARAGRAPH_ALIGNMENT用法未说明 | 新增代码模板 | 避免枚举错误 |
| shape位置为None时崩溃 | 统一过滤None位置shape | 稳定 |
ppt-style-matcher/
├── SKILL.md # 本文件 (v4)
├── scripts/
│ └── analyze_style.py # 风格分析工具 v4
└── references/
└── design-patterns.md # 设计模式参考
| 函数 | 用途 |
|---|---|
analyze_ppt(path, pages, verbose) | 完整风格分析(入口函数,含参考页推荐) |
extract_color_palette(prs, pages) | 5色锁定色板提取(v4:深色适配+角色修正) |
analyze_layout_ratios(slide, prs) | 单页布局比例参数(v4:简单页跳过) |
analyze_rhythm(prs, pages) | 节奏感检测(v4:浅色PPT结构节奏) |
validate_color_usage(prs, palette, indices) | 颜色一致性验证(v4:修复缩进bug) |
validate_layout(prs, indices, palette, keywords) | P0-P3分级质检 |
_is_dark_ppt(prs, pages) | 判断深色主题 |
_pick_best_sample_pages(prs, count) | 智能采样(优先设计良好页) |
set_ea_font(run, font_name) | 设置东亚字体(辅助) |
set_line_spacing(paragraph, pt, ratio) | 设置行距(辅助) |
clean_shape(shape) | 清理自动样式(辅助) |