name: themed-cn-pptx
description: Build, modify, and QA-verify Chinese + IP/character-themed + QR-embeddable EDITABLE PPTX decks using PptxGenJS, with StepFun or MiniMax AI image generation, locked aesthetic recipes, and a deterministic render-QA gate (CJK overflow, text overlap, editable-text, color contrast). Triggers: 可编辑 PPTX, 中文 PPT, PPT 验收, render QA, PptxGenJS, presentation, slide, deck, PowerPoint, 演示文稿, 幻灯片.
📌
Skill 名称:themed-cn-pptx
适用:用 PptxGenJS 构建或修改中文 + IP/角色主题 + 嵌入二维码 的可编辑 PPTX 演示文稿
依赖技能:标准 pptx skill(本 skill 是它的增量补充)
生图能力:集成 StepFun 阶跃星辰和 MiniMax 文生图 API,API Key 从环境变量读取,不硬编码
适合:
- 中文 PPT、IP/角色主题 PPT
- 需要 AI 配图(StepFun / MiniMax)
- 需要嵌入二维码
- 需要 PDF→JPG 渲染 QA 闭环
- 需要可编辑 PPTX(不是图片化幻灯片)
不适合:
- 只要普通商务模板(用标准 pptx skill)
- 只要 PDF 输出(用 PDF 工具)
- 不要求可编辑(直接截图即可)
- 单页海报/非演示文稿(用设计工具)
0. 两条使用路径
本 skill 按用户意图分为两条路径,先判断再执行:
路径 A:改 PPT
用户已有一份 .pptx(或 .js 构建脚本),需要做局部修改——换主题色、加配图、调排版、插页、替换内容。
流程:
- 读取现有
.pptx(用 python-pptx 解析)或 .js 脚本
- 确定修改范围——主题色?哪些页加图?哪些文字要改?
- 修改
build_<theme>.js → 重新生成 .pptx
- QA 循环(§8)
典型说法:
- "把这份 PPT 换成初音主题"
- "给第 3 页和第 6 页各加一张 AI 配图"
- "封面加个背景图,文字加半透明遮罩"
- "粉色再浅一点"
路径 B:从文稿到 PPT
用户给一份文稿/大纲/README/Notion 页,从零生成完整 PPTX。
流程:
- 抓取内容源(粘贴文本 / GitHub README / URL)
- 任务拆解(§1)
- 构建色板(§2)+ 确定生图需求(§2.5)
- 选择 Slide 布局(§5)→ 编写
build_<theme>.js
- 生成配图(§2.5)→ 嵌入 PPTX
- QA 循环(§8)
典型说法:
- "帮我把这个 README 做成 PPT,10 页,初音主题"
- "按这份大纲生成一份演讲稿 PPT"
- "从这篇文档出一份产品介绍 PPT,带配图"
1. 任务拆解(先做这一步)
在写代码前,从用户需求中先抽出:
- IP / 主题 → 映射到 4 色身份(主色 + 副色 + 深底 + 浅底)
- 输出格式 → 确认
.pptx(可编辑)/ PDF Slides / Notion Slides / Presentation App,不要假设
- 语言 → 中文需要 CJK 字体 + 更紧的字号阶(全角字符在同 pt 下比 Latin 宽)
- 硬约束 → 页数上限、二维码目标 URL、需隐去的话题、品牌标识
- 内容源 → 用户粘贴的文本?GitHub README?现有 Notion 页?先抓取再写 slide
- 图像需求 → 哪些页需要 AI 配图?封面背景?卡片插图?项目展示?→ 参考 §2.5 确定用途和尺寸
- 视觉系统 → 对复杂/公开发布 deck,先读
references/aesthetic-rules.md,声明 style_id、布局族、密度和负面风格
- 生图约束 → 生成封面/hero/showcase 前先读
references/image-constraints.md,写出 image manifest 再调用 API
2. 构建角色 IP 调色板(模板)
角色 / IP 主题 deck 需要既像 IP 又不刺眼的配色。固定 6 个色槽:
| 色槽 | 作用 | Miku 示例 |
|---|
| Dominant 主色 | 承载 deck 身份的招牌色 | #39C5BB 初音青(官方) |
| Secondary 副色 | 一种反差强调色(仅用于 header/kicker/callout) | #FF77AA 双马尾粉 |
| Dark BG 深底 | 封面 / 分隔 / 收尾 | #0B1B2B 深夜空蓝 |
| Light BG 浅底 | 内容页 | #F1FBFA 青调米白 |
| Soft accent 柔和点缀 | chips / 浅卡背景 | #E6F8F6 |
| Muted text 弱化文字 | 浅底正文 | #59707B |
权重规则:
- 主色 60-70% 视觉权重,副色 ~20%,强调 ~10%
- 副色绝不用于正文,只用 header / kicker / callout
- IP 有官方色就用官方色,不要猜(先查)
可复用的 JS 常量块:
const C = { miku:"39C5BB", mikuDeep:"1C9990", pink:"FF77AA", pinkSoft:"FFC2DB",
navy:"0B1B2B", navyDeep:"06121E", ink:"0F2233",
paper:"F1FBFA", paperAlt:"E6F8F6", white:"FFFFFF",
textOnDark:"E8FFFD", textOnLight:"0F2233", muted:"59707B", line:"BFE7E2" };
换 IP 时只替换具名颜色,色槽结构保持不变。
预置色板库
非 IP 场景直接从下面挑一套,无需自己配色。每套都经过对比度验证(WCAG AA)。
🏔️ Nord 商务蓝
来源:Nord · 冷调专业,适合技术/SaaS/企业演示。
const C = {
dominant: "88C0D0", dominantDeep: "5E81AC",
secondary: "D08770", secondarySoft: "EBCB8B",
darkBg: "2E3440", darkBgDeep: "242933",
lightBg: "ECEFF4", lightBgAlt: "E5E9F0",
white: "FFFFFF", ink: "3B4252",
textOnDark: "D8DEE9", textOnLight: "3B4252",
muted: "4C566A", line: "D8DEE9"
};
验证:白字 on #2E3440 = 12.5:1 ✅ | #4C566A on #ECEFF4 = 6.4:1 ✅ | 深字 on #88C0D0 = 5.0:1 ✅(浅主色用深字)
🐱 Catppuccin 柔和
来源:Catppuccin Latte · 温暖柔和,适合教育/培训/内部分享。
const C = {
dominant: "1E66F5", dominantDeep: "1A5BD6",
secondary: "8839EF", secondarySoft: "7287FD",
darkBg: "4C4F69", darkBgDeep: "3B3E56",
lightBg: "EFF1F5", lightBgAlt: "E6E9EF",
white: "FFFFFF", ink: "4C4F69",
textOnDark: "EFF1F5", textOnLight: "4C4F69",
muted: "646777", line: "BCC0CC"
};
验证:白字 on #4C4F69 = 8.0:1 ✅ | #646777 on #EFF1F5 = 5.0:1 ✅
💎 Radix 科技蓝
来源:Radix Colors Blue · 无障碍优先,适合产品/技术架构演示。
const C = {
dominant: "0090FF", dominantDeep: "006ADC",
secondary: "6E56CF", secondarySoft: "8B7CE8",
darkBg: "0B1120", darkBgDeep: "060A14",
lightBg: "FBFCFF", lightBgAlt: "F0F4FF",
white: "FFFFFF", ink: "0C1A2B",
textOnDark: "E1E8F5", textOnLight: "0C1A2B",
muted: "5C6B7F", line: "C6D2E0"
};
验证:白字 on #0B1120 = 16.8:1 ✅ | #5C6B7F on #FBFCFF = 6.2:1 ✅
🌿 暖色教育
适合培训课件、K-12 教育、教学分享。色温偏暖,视觉友好。
const C = {
dominant: "179299", dominantDeep: "12787E",
secondary: "FE640B", secondarySoft: "F5A97F",
darkBg: "1A2332", darkBgDeep: "111825",
lightBg: "F7F9F4", lightBgAlt: "EEF2E6",
white: "FFFFFF", ink: "2C3E2D",
textOnDark: "D4E8D5", textOnLight: "2C3E2D",
muted: "587259", line: "C0D4C0"
};
验证:白字 on #1A2332 = 15.8:1 ✅ | #587259 on #F7F9F4 = 5.0:1 ✅
🎓 学术靛
适合论文答辩、学术报告、研究分享。深沉内敛。
const C = {
dominant: "3F51B5", dominantDeep: "303F9F",
secondary: "7C4DFF", secondarySoft: "B388FF",
darkBg: "1A1A2E", darkBgDeep: "12121F",
lightBg: "F5F5FA", lightBgAlt: "EBEBF5",
white: "FFFFFF", ink: "1A1A2E",
textOnDark: "D0D0E8", textOnLight: "1A1A2E",
muted: "5C5C7A", line: "C0C0DA"
};
验证:白字 on #1A1A2E = 14.2:1 ✅ | #5C5C7A on #F5F5FA = 5.5:1 ✅
🌙 暗色创意
适合创意提案、设计评审、夜间/暗室演示。高对比暗色系。
const C = {
dominant: "89B4FA", dominantDeep: "74C7EC",
secondary: "F5C2E7", secondarySoft: "CBA6F7",
darkBg: "1E1E2E", darkBgDeep: "11111B",
lightBg: "2A2A3C", lightBgAlt: "252536",
white: "FFFFFF", ink: "11111B",
textOnDark: "CDD6F4", textOnLight: "CDD6F4",
muted: "8C92AE", line: "45475A"
};
验证:#CDD6F4 on #1E1E2E = 11.3:1 ✅ | #8C92AE on #2A2A3C = 4.6:1 ✅ | 深字 on #89B4FA = 8.9:1 ✅(浅主色用深字)
色板选择决策树
有 IP 官方色? ── 是 ── 用 IP 色 + §2 6 色槽规则
│
否
│
场景是什么?
├── 科技/企业/SaaS ──→ Nord 商务蓝 或 Radix 科技蓝
├── 教育/培训 ──→ 暖色教育 或 Catppuccin 柔和
├── 学术/研究 ──→ 学术靛
├── 产品/设计 ──→ 暗色创意(暗室)或 Catppuccin 柔和(亮室)
└── 不知道 ──→ Nord 商务蓝(最安全)
更多设计理论参考 design-principles.md。
2.1 色彩方案与 QA 规则
中文 PPT 的色彩 QA 不看“感觉”,先按 sRGB / RGB 色号计算。RGB 通道不是线性亮度;先把 #RRGGBB 的 R/G/B 从 0-255 归一化并做 sRGB gamma 线性化,再算相对亮度:
L = 0.2126 * R_linear + 0.7152 * G_linear + 0.0722 * B_linear
contrast = (L_lighter + 0.05) / (L_darker + 0.05)
绿色通道对亮度贡献最大,蓝色最小,所以不要用 RGB 数值差或“看起来颜色不一样”判断可读性。正文对比度至少 4.5:1;大标题、粗体大字、UI 边框、图形对象至少 3:1。用 scripts/color-qa.mjs 快速检查:
node scripts/color-qa.mjs --fg 0F2233 --bg F1FBFA --role body
node scripts/color-qa.mjs --palette 0F2233,F1FBFA,39C5BB,FF77AA --role body
色彩方案选择
| 方案 | 适用 | 规则 |
|---|
| Neutral + Accent | 中文说明型、产品介绍、交付物 | 最稳。正文只用深墨色/近白,品牌色只做条纹、编号、图标、强调块 |
| Monochrome 单色系 | 严肃、科技、统一感强的 deck | 必须拉开明度阶,不要只改饱和度;正文仍用深/浅中性色 |
| Analogous 邻近色 | 柔和、情绪统一的角色主题 | 需要一个深底和一个浅底承载文字,否则容易“一片糊” |
| Complementary 互补色 | 封面、章节页、强冲突观点 | 一方做主色,另一方只做 5-10% 强调;不要互相做正文/背景 |
| Split-complementary 分裂互补 | IP 主题、活泼但可控 | 比纯互补更安全,适合“主色 + 两个小强调色” |
| Triadic / Tetradic | 流程图、矩阵、分类图 | 只用于图形编码;同页高饱和主色不超过 3 个 |
| Dark mode 深底 | 封面、收尾、章节分隔 | 用 textOnDark 近白,不用纯高饱和色写长正文;图片上文字必须加遮罩 |
颜色组合负面清单
这些组合默认判为风险,除非能证明对比度和场景都安全:
- 正文对比度 < 4.5:1:任何小字号中文、脚注、URL、表格正文都禁止。
- 标题 / 图形 / UI 对比度 < 3:1:大标题、标签、边框、图标、数据图例都禁止。
- 副色当正文:例如高饱和粉、青、黄直接写在浅底上,通常会失败;副色只做 kicker、短线、编号、callout。
- 红 + 绿表达状态:不要只靠红绿区分成功/失败;必须加文字、图标或形状。
- 蓝字压红/橙底,或红/橙字压蓝底:CJK 小字边缘容易震动,除非是大号短标题且对比度足够。
- 两个高饱和色互为文字/背景:如亮青压亮粉、亮黄压亮蓝;用中性色隔开。
- 同色相低明度差:同一 hue 只改一点点亮度/饱和度,容易看成一片;相邻层级必须拉开亮度。
- 浅灰压浅底 / 深灰压深底:灰度很容易“过关感知不过关”,用脚本先算。
- AI 图上裸放文字:不允许直接在复杂图片上放正文;加 40-55% 深色遮罩或独立文字底板。
- 一页超过 3 个高饱和主视觉色:分类图例可以多色,普通内容页不行。
- 全 deck 只有一个 hue 家族且没有中性深浅阶:会变成单色糊;至少保留深底、浅底、正文色、弱化文字色。
- PptxGenJS 色号带
#:代码里始终写 "39C5BB",不要写 "#39C5BB"。
QA 时列出实际使用的 fg/bg 对,至少检查:正文 on 浅底、正文 on 深底、标题 on 封面图遮罩、表格文字 on header、QR URL、footer、图例文字。
2.5 AI 生图 — 尺寸适配(StepFun / MiniMax)
通过 lib/ai-image.js 生成配图,自动适配 PPT 排版。StepFun 是默认和重点推荐 provider;MiniMax 作为可选补充。两者都支持国内版 / 国际版。不硬编码 API Key,从环境变量或 .env 读取。新脚本优先导入 ai-image.js;旧脚本继续导入 stepfun-image.js 也能运行(兼容 re-export)。
引入方式
import { generateSlideImage, addImageToSlide, addImageOverlay, SIZE_MAP } from "./lib/ai-image.js";
const img = await generateSlideImage({
provider: "stepfun-cn",
prompt: "赛博朋克城市夜景,中文发布会封面背景,留出标题区域",
usage: "cover",
});
if (img) {
slide.addImage({ path: img.localPath, x: 0, y: 0, w: img.pptxLayout.w, h: img.pptxLayout.h });
}
Provider 选择
优先级:
generateSlideImage({ provider: "stepfun" | "minimax" })
- 环境变量
PPT_IMAGE_PROVIDER 或 AI_IMAGE_PROVIDER
- 如果只有
MINIMAX_API_KEY,自动选择 minimax
- 默认
stepfun-cn,保证旧脚本行为不变,并优先走 StepFun 国内开放平台
可直接使用 region alias:
await generateSlideImage({ provider: "stepfun-cn", prompt, usage: "cover" });
await generateSlideImage({ provider: "stepfun-global", prompt, usage: "cover" });
await generateSlideImage({ provider: "minimax-cn", prompt, usage: "card" });
await generateSlideImage({ provider: "minimax-global", prompt, usage: "card" });
用途 → 尺寸映射
| 用途 | PPT 场景 | StepFun 尺寸 | MiniMax 比例 | PPTX (英寸) |
|---|
cover | 封面全幅背景 | 1360×768 | 16:9 | { w:10, h:5.625 } |
coverOverlay | 带文字遮罩的封面背景 | 1360×768 | 16:9 | { w:10, h:5.625 } |
hero | 上半区横幅 | 1360×768 | 16:9 | { w:10, h:3 } |
bannerWide | 超宽横幅 | 1360×768 + 裁切 | 21:9 | { w:10, h:2.45 } |
ultraWideHero | 超宽首页/章节视觉 | 1360×768 + 裁切 | 21:9 | { w:10, h:2.8 } |
sideStrip | 右侧竖版装饰条 | 768×1360 | 9:16 | { w:2.5, h:4.44 } |
card | 方形卡片配图 | 1024×1024 | 1:1 | { w:2.5, h:2.5 } |
cardTall | 竖版卡片配图 | 896×1184 | 3:4 | { w:2.3, h:3.04 } |
cardWide | 横版卡片配图 | 1184×896 | 4:3 | { w:3.5, h:2.65 } |
showcase | 产品/项目展示 | 1184×896 | 4:3 | { w:3.9, h:2.95 } |
phoneMockup | 手机竖屏 mockup | 768×1360 | 9:16 | { w:1.8, h:3.2 } |
icon | 小图标/占位图 | 512×512 | 1:1 | { w:1.5, h:1.5 } |
适配规则:
- StepFun
step-image-edit-2 使用官方 size 字符串:1024x1024、768x1360、896x1184、1360x768、1184x896。文档标注该字符串是 height x width,但在本 skill 中按视觉用途映射到 PPT:cover/hero 固定用 1360x768,showcase/cardWide 固定用 1184x896,phoneMockup/sideStrip 固定用 768x1360。
- StepFun 当前单次文生图按 1 张处理;如需要多张候选图,循环调用,不依赖
n > 1。
- MiniMax 优先使用
aspect_ratio 而不是 width/height:16:9、4:3、1:1、3:4、9:16。只有确实需要自定义像素时才传 width/height,并保证 512-2048 且能被 8 整除。
cover、hero、bannerWide、ultraWideHero、showcase、phoneMockup 不手写尺寸,统一从 SIZE_MAP 或 getImageUsageConfig() 取,避免把 MiniMax ratio 和 StepFun size 混用。
bannerWide / ultraWideHero:MiniMax 原生 21:9;StepFun 用 1360x768 生成,再按 cropPolicy 和 safeZone 放入 PPT。
- 返回 URL 一律立即下载到
assets/<provider>/,PPTX 只引用本地文件,避免 StepFun/MiniMax 临时链接过期。
环境变量配置
export PPT_IMAGE_PROVIDER=stepfun
export PPT_IMAGE_REGION=cn
export STEPFUN_API_KEY=sk-xxx
export STEPFUN_REGION=cn
export STEPFUN_API_MODE=platform
export MINIMAX_API_KEY=sk-xxx
export MINIMAX_REGION=global
ai-image.js 会自动加载项目根目录 .env,但 shell / MCP / CI 中已有的 process.env 优先。
Provider 参数
- StepFun:使用
/images/generations,按具体 size 生成,适合精确 PPT 预设。
- MiniMax:使用
/image_generation,按 aspect_ratio 生成;返回 URL 时工具会立即下载到本地,避免 URL 过期影响 PPT。
- StepFun 国内开放平台默认 Base URL:
https://api.stepfun.com/v1
- StepFun 国际开放平台默认 Base URL:
https://api.stepfun.ai/v1
- StepFun Step Plan 国内 / 国际专属路径:
https://api.stepfun.com/step_plan/v1 / https://api.stepfun.ai/step_plan/v1
- MiniMax 国内 / 国际默认 Base URL:
https://api.minimaxi.com/v1 / https://api.minimax.io/v1
- MiniMax 可传
promptOptimizer、seed、n、subjectReference:
const img = await generateSlideImage({
provider: "minimax",
prompt,
usage: "showcase",
promptOptimizer: true,
seed: 42,
});
StepFun 可传 steps、cfgScale、negativePrompt、textMode、seed、apiMode:
const img = await generateSlideImage({
provider: "stepfun-cn",
prompt,
usage: "cover",
steps: 8,
cfgScale: 1.0,
textMode: true,
});
开放平台申请与使用指南(StepFun 推荐)
- 选择版本:国内账号用
https://platform.stepfun.com,国际账号用 https://platform.stepfun.ai。
- 注册 / 登录后进入用户中心或 API Key 页面。
- 创建 API Key。只放在本地
.env、系统环境变量或 CI Secret,不写进源码。
- 普通开放平台调用用
STEPFUN_API_MODE=platform;如已订阅 Step Plan 且要走专属路径,用 STEPFUN_API_MODE=step_plan。
- 在构建目录创建
.env:
PPT_IMAGE_PROVIDER=stepfun
PPT_IMAGE_REGION=cn
STEPFUN_API_KEY=sk-xxx
STEPFUN_REGION=cn
STEPFUN_API_MODE=platform
- 先跑
npm test 验证导入、provider/region 解析、无 key 降级。
- 构建 PPT 时优先使用
provider: "stepfun-cn";国际版账号改为 provider: "stepfun-global" 或设置 STEPFUN_REGION=global。
图片排版规范
- 深底图片上放文字 → 必须加半透明遮罩(
addImageOverlay),透明度 40-50%
- 注意:
addImageOverlay(slide, pres, { opacity: 45 }) 中的 opacity 是用户接口参数,传 40–50 的整数;内部映射到 PptxGenJS 的 transparency。不要传 0.18 这种比例值。
- 卡片内图片 → 上下留 0.15″ padding,用
rounding: true 圆角
- 封面背景图 → 叠加深色半透明矩形 + 文字层,确保文字可读
- 图片不贴文字 → 左右至少 0.2″ 间距
- 无 API Key 时 → 自动跳过生图,console.warn 提示,不阻断 PPT 生成
模型尺寸对照
| Provider | 模型 | 支持尺寸 / 比例 |
|---|
| StepFun | step-image-edit-2(默认) | 1024×1024, 768×1360, 896×1184, 1360×768, 1184×896 |
| StepFun | step-2x-large | 256×256, 512×512, 768×768, 1024×1024, 1280×800, 800×1280 |
| StepFun | step-1x-medium | 256×256, 512×512, 768×768, 1024×1024, 1280×800, 800×1280 |
| MiniMax | image-01 | 1:1, 16:9, 4:3, 3:2, 2:3, 3:4, 9:16, 21:9 |
3. 中文字体默认
- 标题 & 正文字体:
Microsoft YaHei(LibreOffice 和 PowerPoint 渲染都 OK,CJK + Latin 都支持)
- 等宽:
Consolas(URL / 代码 / 框架名)
- CJK 安全字号(16:9,10×5.625″):
| 用途 | 字号 | 说明 |
|---|
| 封面主标题(CJK + 括号) | 44pt | 56pt 会溢出——「」是全角,占位多 |
| 章节标题 | 28pt | 9″ 宽下一行中文够用 |
| 卡片 / 引擎标题 | 16pt bold | |
| 正文 | 11–12pt | 中文 11pt 仍很清晰 |
| 大数字 stat | 60–80pt | Latin / 数字,放心放大 |
| Kicker(英文大写) | 11–12pt + charSpacing: 4–6 | 竖线序号下的拉丁字母 letter-spacing |
CJK 坑:
- 大量「」括号的标题,比纯 CJK 标题字号要再小 ~25%
- 估算:CJK 字符宽度 ≈ fontSize × 0.95pt
charSpacing 对中文很丑,只在英文 kicker 上加
4. 重复装饰 = 品牌一致性
把三个 helper 函数提出来,每页都调用:
function mikuStripe(slide) {
slide.addShape(pres.shapes.RECTANGLE, { x:0, y:0, w:SW, h:0.08, fill:{color:C.miku}, line:{color:C.miku} });
slide.addShape(pres.shapes.RECTANGLE, { x:0, y:0.08, w:SW, h:0.025, fill:{color:C.pink}, line:{color:C.pink} });
slide.addShape(pres.shapes.RECTANGLE, { x:0, y:SH-0.04, w:SW, h:0.04, fill:{color:C.miku}, line:{color:C.miku} });
}
function sectionTitle(slide, kicker, title) {
slide.addShape(pres.shapes.RECTANGLE, { x:0.5, y:0.45, w:0.18, h:0.18, fill:{color:C.pink}, line:{color:C.pink} });
slide.addText(kicker, { x:0.75, y:0.38, w:6, h:0.3, fontSize:12, bold:true, color:C.miku, charSpacing:4, margin:0 });
slide.addText(title, { x:0.5, y:0.68, w:9, h:0.7, fontSize:28, bold:true, color:C.ink, margin:0 });
slide.addShape(pres.shapes.RECTANGLE, { x:0.5, y:1.42, w:0.9, h:0.05, fill:{color:C.miku}, line:{color:C.miku} });
slide.addShape(pres.shapes.RECTANGLE, { x:1.42, y:1.42, w:0.3, h:0.05, fill:{color:C.pink}, line:{color:C.pink} });
}
function footer(slide, n, total) {
slide.addText("Brand · Subtitle", { x:0.5, y:SH-0.35, w:6, h:0.25, fontSize:9, color:C.muted, margin:0 });
slide.addText(`${n} / ${total}`, { x:SW-1.2, y:SH-0.35, w:0.7, h:0.25, fontSize:9, color:C.muted, align:"right", margin:0 });
}
这三件套,能把 10 页临时排版瞬间变成一套有设计的 deck。
💡
"长主色 + 短副色" 双段下划线是性价比最高的品牌符号。不需要任何图片素材,立刻有「设计过」的观感。
5. Slide 类型菜单
不要每页重新发明 layout。从这些里挑。每个布局的必需槽位(stripe / title / footer / pageBadge / image)和允许的生图 usage 已登记在 references/layout-slots.md —— 那是一份可被 scripts/render-qa.mjs --contract 校验的契约,相当于 HTML deck 的 data-layout 注册表。交付前用 render-qa 跑一遍,缺槽位会报 P2。
基础布局(无图)
| # | 类型 | 何时用 | 核心元素 |
|---|
| 1 | 封面(深底) | 第 1 页 | 角落半透明大椭圆 + kicker 胶囊 + 多行堆叠主标题 + 强调 bar + 作者块 |
| 2 | 引言 + 三支柱 | 定位 / 定义 | 带副色左 bar 的大引用卡 + 下方 3 个图标圆 mini-card |
| 3 | 双卡 + verdict | 两股对比力量 | 上 2 张白卡 + 下方一个深底 callout 写「所以呢」 |
| 4 | 条纹表格 | 3–5 行对比 | 主色 header bar + 交替白 / paperAlt 行 + 副色编号圆圈 |
| 5 | 矩阵 | 映射 / 同构 | N 列 × 主/副/白 横向色带网格 |
| 6 | 项目展示 | 突出某个项目 | 左侧深色卡 + 巨型数字 stat + 右侧 2×3 特性网格 |
| 7 | 双栏映射 | A ↔ B 等价 | 两侧 header bar + 交替行 + 中间箭头线 |
| 8 | 问题堆叠 | 讨论提问 | Q1/Q2/Q3 卡堆叠 + 彩色字母圆 + 左侧色 bar |
| 9 | 收尾 + QR(深底) | 最终 CTA | 双色大标题 + 左侧联系列表 + 右侧 QR 卡(带框) |
带图布局(AI 生图增强)
| # | 类型 | 何时用 | 核心元素 + 生图 |
|---|
| 1a | 封面(深底+背景图) | 需要视觉冲击 | cover 1360×768 全幅背景 + 深色遮罩 + 文字层 |
| 3a | 双卡 + 配图 + verdict | 对比 + 视觉辅助 | 每张白卡嵌 card 1024×1024 方形图 |
| 3b | 双卡 + 竖版配图 + verdict | 对比 + 竖版展示 | 白卡嵌 cardTall 896×1184 竖版图 |
| 6a | 项目展示 + 配图 | 产品/界面展示 | 左侧 cardWide/showcase 1184×896 图 + 右侧特性网格 |
| 6b | 项目展示 + 竖版 mockup | 手机 App 展示 | 左侧 phoneMockup 768×1360 + 右侧特性 |
| 7a | 双栏映射 + 侧栏图 | 映射 + 装饰 | 右侧 sideStrip 768×1360 竖版装饰条 |
用 8–10 张。一个 deck 不要超过 5 种 layout,否则视觉碎片化。
带图布局示例代码
1a. 封面(深底+背景图)
const coverImg = await generateSlideImage({
prompt: "抽象科技流动线条,深蓝色调,初音未来风格",
usage: "cover",
});
const slide = pres.addSlide();
if (coverImg) {
slide.addImage({ path: coverImg.localPath, x: 0, y: 0, w: 10, h: 5.625 });
addImageOverlay(slide, pres, { color: C.navy, opacity: 45 });
} else {
slide.background = { fill: C.navy };
}
slide.addText(kicker, { ... });
slide.addText(mainTitle, { ... });
mikuStripe(slide);
3a. 双卡 + 配图 + verdict
const cardImg1 = await generateSlideImage({ prompt: "自然语言文本界面", usage: "card" });
const cardImg2 = await generateSlideImage({ prompt: "编程代码界面", usage: "card" });
const slide = pres.addSlide();
sectionTitle(slide, "CONTRAST", "语言 vs 编程");
slide.addShape(pres.shapes.RECTANGLE, { x:0.5, y:1.7, w:4.4, h:2.0, fill:{color:C.white}, shadow:... });
if (cardImg1) {
slide.addImage({ path: cardImg1.localPath, x: 0.7, y: 2.2, w: 1.5, h: 1.5, rounding: true });
}
slide.addText("标题", { x:2.3, y:2.25, w:2.4, h:0.45, fontSize:16, bold:true, color:C.ink });
6a. 项目展示 + 配图
const showcaseImg = await generateSlideImage({ prompt: "现代化仪表盘界面", usage: "showcase" });
const slide = pres.addSlide();
sectionTitle(slide, "SHOWCASE", "核心产品");
if (showcaseImg) {
slide.addImage({ path: showcaseImg.localPath, x: 0.5, y: 1.75, w: 3.9, h: 2.95, rounding: true });
} else {
slide.addShape(pres.shapes.RECTANGLE, { x:0.5, y:1.75, w:3.9, h:2.95, fill:{color:C.navy} });
}
6. 无网络生成二维码
沙箱默认无网络——pip install qrcode 会失败。用 reportlab 内藏的 QR 编码器(预装)+ PIL 自己栅格化:
from reportlab.graphics.barcode.qrencoder import QRCode, QRErrorCorrectLevel, QR8bitByte
from PIL import Image
data = "https://github.com/CacinieP"
for v in range(1, 20):
try:
qr = QRCode(v, QRErrorCorrectLevel.H)
qr.addData(QR8bitByte(data))
qr.make()
modules = qr.modules
break
except Exception:
continue
n, scale, border = len(modules), 16, 4
size = (n + border*2) * scale
img = Image.new("RGB", (size, size), (255,255,255))
px = img.load()
for r in range(n):
for c in range(n):
if modules[r][c]:
for dy in range(scale):
for dx in range(scale):
px[(c+border)*scale+dx, (r+border)*scale+dy] = (0,0,0)
img.save("/data/qr.png")
然后在 PptxGenJS 中嵌入:
slide.addImage({ path: "/data/qr.png", x: 6.95, y: 2.15, w: 2.4, h: 2.4 });
给 QR 加框:白色圆角卡 + 主色边 + 主色顶 strip 写 SCAN · 扫码访问,URL 用等宽字体放在码下方。深底上裸放 QR 像故障。
7. 踩过的坑
- CJK 主标题溢出 — 封面 56pt + 全角「」导致「训练场」换到第三行撞副标题。修:44pt 一行,或拆成有意识的多行 + 间距
- 超窄文本框被裁 — 0.1″ 宽的竖向标签会渲染成断行碎片。别用低于 0.4″ 的文本容器;要竖排就用 rotate,不用窄盒
reportlab.renderPM 在沙箱里坏的(缺 rlPyCairo)。不要用 renderPM.drawToFile,手动 PIL 栅格化
pip install 没用。先看预装列表,不要为没装的库设计架构
- LibreOffice CJK 字体 fallback:找不到
Microsoft YaHei 会落到 Noto Sans CJK SC,也 OK。别用渲染管线没有的冷门中文字体
shadow 对象复用会污染第二个形状。一定写成 () => ({...}) 工厂函数,每次形状调用
- 颜色不要带
#:永远 "39C5BB" 不是 "#39C5BB"。透明度也不要编进 8 位 hex,用 opacity: 0.18
- PptxGenJS 没有
TRIANGLE 形状 → 用 LINE 替代箭头指示
- 封面背景图比例 — 必须用 1360×768(16:9)精确匹配幻灯片,其他比例会裁切或留白
8. 强制 QA 循环
先跑自动化门禁,再看图。 三条命令构成 P0 gate,任一不过都不要交付:
npm run qa:render -- output/deck.pptx --fix-hints \
--contract references/layout-slots.md
python3 ../../scripts/pptx-editable-check.py output/deck.pptx
node ../../scripts/color-qa.mjs --palette <你的色板> --role body
生成前用免渲染估算器预防 CJK 溢出(比渲染一轮再发现快得多):
node ../../scripts/cjk-overflow-check.mjs --text "你的标题" --font-size 44 --box-width 9
自动化门禁全绿后,再做视觉核对(渲染成图逐页挑刺):
soffice --headless --convert-to pdf deck.pptx
pdftoppm -jpeg -r 100 deck.pdf slide
8.1 对比度自检(WCAG AA)
在视觉 QA 前,先用 JS 脚本量化检查所有文字/背景对比度:
function hexToRgb(hex) {
const h = hex.replace("#","");
return { r: parseInt(h.slice(0,2),16), g: parseInt(h.slice(2,4),16), b: parseInt(h.slice(4,6),16) };
}
function relativeLuminance({r,g,b}) {
const [rs,gs,bs] = [r,g,b].map(c => {
const s = c / 255;
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function contrastRatio(hex1, hex2) {
const l1 = relativeLuminance(hexToRgb(hex1));
const l2 = relativeLuminance(hexToRgb(hex2));
const lighter = Math.max(l1, l2), darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
const checks = [
{ label: "深底+白字", fg: "#FFFFFF", bg: "#"+C.darkBg, min: 4.5 },
{ label: "浅底+正文", fg: "#"+C.muted, bg: "#"+C.lightBg, min: 4.5 },
{ label: "主色块+文字", fg: "#"+C.ink, bg: "#"+C.dominant, min: 3.0 },
{ label: "浅底+深字", fg: "#"+C.textOnLight, bg: "#"+C.lightBg, min: 4.5 },
{ label: "深底+浅字", fg: "#"+C.textOnDark, bg: "#"+C.darkBg, min: 4.5 },
];
checks.forEach(c => {
const ratio = contrastRatio(c.fg, c.bg);
const pass = ratio >= c.min ? "✅" : "❌";
console.log(`${pass} ${c.label}: ${ratio.toFixed(1)}:1 (需要 ≥${c.min})`);
});
标准:WCAG AA(正常文字 ≥ 4.5:1,大文字 ≥ 3.0:1)。任何 ❌ 都要调整色值。
8.2 视觉 QA 清单
然后把每张 slide 图加载到 transcript,用挑刺的眼光看:
只重渲染问题页:
pdftoppm -jpeg -r 100 -f N -l N deck.pdf slide-fix
**至少完成一轮「修复 → 复查 → 无新问题」**才算完工。
9. 默认文件布局
skills/themed-cn-pptx/
skill.md # 本文档
references/
aesthetic-rules.md # 视觉系统与美学负面清单
image-constraints.md # 生图 manifest、尺寸、负面 prompt
layout-slots.md # 可校验的布局槽位契约(render-qa --contract 消费)
recipes/ # 锁定审美 recipe:可直接跑的 theme + marks + 布局
recipe-editorial-grid.mjs # 中文编辑设计风(克制、发丝线、低饱和)
recipe-dark-launch.mjs # 深底发布风(大对比、hero 配图、CTA/QR)
design-contract.md # editorial-grid 锁定项说明
design-contract-darklaunch.md
lib/
ai-image.js # StepFun / MiniMax 通用生图工具库
stepfun-image.js # 旧脚本兼容入口,re-export ai-image.js
cjk-text.js # CJK 宽度估算(QA 工具共享)
pptx-shapes.js # OOXML slide 解析器(render-qa 用)
zip-reader.js # 零依赖 .pptx zip 读取器(render-qa 用)
examples/
build_<theme>.js # 可选:示例构建脚本
项目构建目录:
/data/
build_<theme>.js # PptxGenJS 脚本
lib/
ai-image.js # 生图工具库副本
stepfun-image.js # 兼容旧脚本时才需要
assets/
stepfun/ # StepFun 生成的图片
YYYY-MM-DD-xxxx.png
minimax/ # MiniMax 生成的图片
YYYY-MM-DD-xxxx.png
qr.png # 收尾页二维码
<output>.pptx # 最终交付
<output>.pdf # QA 渲染
slide-*.jpg # QA 视觉
把 .js 和 .pptx 一起留着,用户后续要微调时可以局部改,不用从零重来。
10. 完工前清单
通用
对比度与无障碍
AI 生图
改 PPT 路径额外检查
从文稿到 PPT 路径额外检查