1. 一句话:这是要做什么?
你有没有过这种经历:老板丢来一个主题,让你今晚出一套 PPT。你打开 PowerPoint,从空白页开始,一页一页排版、调字号、找配图……两小时过去了,才做了 5 页。
我们想做一个新东西,让这件事变得像「点外卖」一样简单:
你只要输入一个主题、或者丢进来一份文档,AI 就帮你把整套幻灯片生成好。生成之后,你可以在网页里像用 PowerPoint 一样自由编辑,改字、挪框、换图都行。最后,一键导出成能继续编辑的 .pptx 文件,或者发给别人看的 .pdf。
简单说:以前是「PPTX 进 → 视频出」,这次是「内容进 → PPTX 出」。我们不做配音、不做数字人讲解,就专注做好两件事:让你编辑得爽,导出得准。
2. 为什么我们自己造轮子,不直接用现成的?
你可能想问:网上不是有很多开源工具吗?直接拿来用不就行了?
我们认真调研了一圈,结论是:能用的零件我们都有,缺的那一环,外面要么是坑、要么是锁。
2.1 三条路,各有各的坑
市面上做「AI 生成 PPT」的,基本就三条技术路线:
| 路线 | 听起来 | 实际体验 |
|---|---|---|
| A. 网页排版 → 转成 PPT | 排版最自由,想怎么摆怎么摆 | 转换容易错位,一页好看,下一页就乱了 |
| B. 套模板 → 填内容 | 最稳,不容易出错 | 视觉中规中矩,但胜在「真能编辑」 |
| C. 生成一张图 → 拆成图层 | 图最好看,还能「选中文字」 | 是假的!字和背景是两层贴上去的,一放大就重影,改一个字都费劲 |
路线 C 最迷惑人——它生成的图确实漂亮,但「可编辑」是事后硬拆出来的,就像把一张照片里的字「抠」出来,看着能选,其实一改就露馅。这种方案适合「只要好看、能改几个字」的场景,当不了正经产品的地基。
2.2 两个必须避开的「坑」
调研时我们还发现两个大坑,差点踩进去:
坑一:大厂官方的 PPT 生成工具,居然不是开源的。
有个知名 AI 公司官方出的 PPT 生成代码,看着很香,但它的授权协议明确写了:不能商用、不能复制、不能改、不能分发。我们做的是正经商业产品,碰都不能碰。
坑二:一个很火的 9000 星开源项目,核心功能是「锁起来」的。
它表面上是开源的,但真正负责「把网页转成 PPT」的核心代码,是编译好的、看不到源码的二进制文件。也就是说,最关键的「可编辑」那一环,恰恰是人家藏起来不给你看的。
结论: 我们自己手里已经有 80% 的零件(导出工具、渲染引擎、账号体系都有了),唯一缺的那一环,外面要么是坑、要么是锁。所以,自己造。
3. 我们是怎么设计的?几个关键决定
这一节是这篇文章的「干货」,但我会尽量用人话讲。这几个决定,直接决定了这个产品好不好用、能不能做好。
3.1 关键决定一:AI 只负责「想」,不负责「画」
很多人以为 AI 生成 PPT 是 AI 直接在一张白纸上画格子、写字。如果真这么做,出来的东西一定是灾难:字叠在一起、跑出页面、字号忽大忽小。
我们的做法是分工明确:
- AI 只做两件事:① 想出这一页写什么内容(标题、要点、数据);② 决定这一页用什么版式(比如「大标题 + 三张卡片」还是「左边图、右边文字」)。
- 一个「排版引擎」负责把版式变成精确的坐标,像搭积木一样,把 AI 想好的内容稳稳地放到页面上。
这样,AI 负责「聪明」,排版引擎负责「靠谱」。而且,排版引擎只在生成初稿时起作用,生成之后,你想怎么拖、怎么改,完全自由。
3.2 关键决定二:让「一键换风格」成为可能
你有没有想过,为什么很多 PPT 工具换主题色,换完总有几个地方颜色不对?
因为很多工具存的是「这个字是 24 号、这个颜色是 #1a1a1a」这种写死的数值。换风格的时候,它得从数值反推「这个字是标题还是正文」,推不准就换不干净。
我们的做法是:每个元素不存「具体长什么样」,而是存「它是什么角色」。比如「这是标题」「这是正文」「这是卡片标题」。换风格的时候,系统只要把「标题」对应的新样式套上去,就完事了。你手动改过的地方,单独存一份「覆盖记录」,不会被换风格冲掉。
一句话:我们存「语义」,不存「像素」。 这是「一键换风格」能不能做成的分水岭。
3.3 关键决定三:模板不是「一整页」,而是「积木」
传统 PPT 模板是「一整页一整页」的,比如「模板 A 的第 3 页长这样」。这种模板的坏处是:每加一套新风格,所有页面都要重新画一遍,维护成本爆炸,而且用户改不动。
我们的做法是拆积木:
- 风格(比如商务蓝、科技暗色、杂志风)—— 5 到 10 套,用户选。
- 版式组件(比如标题、三卡、对比、时间线)—— 25 到 40 个,排版引擎自动选。
用户看到的「模板」= 风格 × 组件。10 套风格 × 30 个组件,视觉上像几百套模板,但我们的维护量只是 10 + 30。而且用户改起来更灵活。
3.4 关键决定四:字体,我们「锁死」了
这是个很现实的问题:我们的服务器上只有 1 个中文字体,而你的电脑上有 99 个。如果服务器用它的字体生成 PDF,你打开一看,字全变了。
我们的解决方案是:生成端只允许用一套白名单字体(思源黑体,免费、可商用、可嵌入),服务器也装上它。这样:
- 导出的 PDF 是100% 保真的,因为是在服务器渲染的,不依赖你电脑的字体。
- 导出的 PPTX 里,如果你电脑没装思源黑体,会自动用系统默认的黑体(微软雅黑/苹方)替代——都是黑体,观感非常接近,是代价最小的选择。
代价是:你不能在生成端随便换字体。这是主动放弃,我们会写进产品说明。
3.5 关键决定五:质量检查,只查「结构」,不查「像素」
生成完 PPT,我们要检查它好不好看。但这里有个系统性的难题:服务器渲染的字形,和你电脑上渲染的字形,天生就有细微差别。所以,我们不追求「逐像素相同」,只检查「结构性错误」:
| 我们能查 | 我们查不了 |
|---|---|
| 元素有没有重叠 | 文字实际断行位置 |
| 有没有跑出页面边界 | 行距的像素级差异 |
| 文字有没有溢出容器 | 你电脑上的最终观感 |
| 图片有没有缺失、变形 |
唯一 100% 保真的交付格式是 PDF。PPTX 的保真度天花板就是「结构正确 + 字体替换后观感接近」。
3.6 关键决定六:导出不排队
我们的服务器上,可能同时有一个 90 分钟的数字人渲染任务在跑。如果用户这时候点「导出 PPTX」,不能让它排在那个大任务后面等 90 分钟。
所以,我们给「导出」单独开一条快速通道,并发数卡在 1-2 个。反正导出是秒级任务,排队也没感觉,但能防止一群人同时导出把服务器挤爆。
3.7 关键决定七:只做「单向导出」
「在网页里编辑」和「在 PowerPoint 里编辑」是两件事。我们只做从网页导出到 PPTX,不做「你在 PowerPoint 里改完再传回来同步」——那是个无底洞,我们不做。
4. 这个产品到底能做到什么?边界在哪?
4.1 能做到的
生成
- 输入:主题 / 大纲 / 上传文档(doc、pdf、txt)→ 一次出整套
- 版式:由组件库决定,排版引擎按内容量自动选
- 配图:AI 生图 / 图库 / 用户上传
- 风格:一键换配色与字体方案
编辑
- 自由画布:拖拽、缩放、旋转、栅格吸附、参考线
- 图层:文字、图片、形状、图标、线段
- 页:新增 / 删除 / 复制 / 排序
- 文本:字号、颜色、粗斜体、对齐、行距、项目符号
- 图片:上传、裁剪、圆角、替换
- 图层树:层级、锁定、隐藏、编组
- 撤销重做、对齐分布、备注栏
导出
.pptx:原生文本框 + 形状 + 图片,能改字、能挪框、能换图.pdf、每页 PNG
4.2 打折扣的(能做,但别期待满分)
| 能力 | 现实 | 原因 |
|---|---|---|
| 复杂视觉(渐变、阴影、混合模式) | 降级成图片层。看着对,但在 PowerPoint 里改不了 | PPTX 没有对应原语 |
| 图表 | 简单图表可做原生;定制样式退回图片 | 工具库图表 API 有限 |
| 表格 | 基础表格可以;合并单元格、条件格式不行 | 同上 |
| 导出保真 | 「看着差不多」,不是逐像素相同 | PowerPoint 与浏览器对换行、行距的计算本就不同 |
| 字体 | 只能给白名单 | 不锁死就必然错位 |
| 主题 / 母版 | 能做 token 级主题,做不了 PowerPoint 的 Slide Master 继承 | 概念映射不完整 |
4.3 做不到的(再努力也绕不过)
A. PPTX 格式的天花板
- 动画与切换效果 —— 工具库不支持创建
- SmartArt —— 同上
- 字体嵌入 —— 授权问题
- 公式(OMML)
→ 这四项只有一条出路:商业授权组件。要不要为了动画买它,是纯商业决定。
B. 架构上明确不做的
- 用户上传 PPTX 反解成可编辑图层
- 与 PowerPoint 双向同步
- 多人实时协作(技术上能做,但工作量巨大,可后加)
4.4 产品文案的红线
别写「导出的 PPT 可以随意编辑」。 用户一试形状和动画就会觉得被骗。
准确说法:「导出的 PPTX 里,文字都能直接改」。
5. 我们打算怎么把它做出来?
5.1 模块划分
| 模块 | 做什么 |
|---|---|
| 编辑器页面 | 矢量画布编辑器 + 风格/版式面板 |
| 渲染层 | 组件库(25–40 个版式组件) |
| 主题 | token 表,5–10 套 |
| 后端 | 增删改查 + 生成编排 + 导出派发 |
| 排版引擎 | AI 内容 + 版式意图 → 精确坐标 |
| 导出器 | 内部模型 → pptx |
| QA 环 | 生成侧回渲比对 |
5.2 核心数据模型
设计空间统一 1920×1080。换算干净:导出时 pt = px / 2。
// Deck
{
"id": "...",
"title": "...",
"themeId": "business-blue",
"designW": 1920, "designH": 1080,
"slides": [ /* Slide */ ]
}
// Slide
{
"id": "...",
"index": 0,
"layout": "three-cards", // 版式组件名,用于「换版式」
"background": { "token": "bg-base" }, // 或 { "image": "oss://..." }
"notes": "这一页的讲稿",
"layers": [ /* Layer */ ]
}
// Layer —— 注意这里只有 token 引用,没有绝对样式
{
"id": "...",
"kind": "text" | "image" | "shape",
"role": "title" | "body" | "caption" | "card-title",
"box": { "x": 120, "y": 80, "w": 800, "h": 120 },
"rotation": 0,
"z": 3,
"locked": false,
"visible": true,
"style": { // 全部是 token / 刻度引用
"colorToken": "fg-primary",
"fontToken": "font-display",
"sizeStep": 3, // 字号刻度,不是 px
"weight": "bold",
"align": "left",
"lineHeight": 1.4
},
"overrides": { }, // 用户手改,覆盖 token
"text": "...", // kind = text
"src": "oss://...", // kind = image
"shape": "rounded-rect" // kind = shape
}
主题 token 表(每套风格一份):
{
"id": "business-blue",
"colors": {
"bg-base": "#ffffff", "bg-accent": "#f1f5f9",
"fg-primary": "#0f172a", "fg-secondary": "#64748b",
"brand": "#2563eb", "brand-fg": "#ffffff"
},
"fonts": { "font-display": "Source Han Sans", "font-body": "Source Han Sans" },
"scale": { "size-steps": [14, 18, 24, 32, 44, 60], "space-steps": [8,16,24,40,64] },
"shape": { "radius": 12, "shadow": "sm" }
}
5.3 落库
沿用现有「整体保存一页」的模式:
ppt_deck (id, user_id, title, theme_id, status, version, created_at, updated_at)
ppt_slide (id, deck_id, idx, layout, background, notes, layers jsonb, version)
layers存jsonb,整页保存,不拆表。编辑器自动保存本来就是一页一提交。- 写 jsonb 必须显式转型
?::jsonb。 - 用
SimpleJdbcInsert时必须指定usingColumns,否则缺列会被绑成 NULL。 - DDL 无迁移框架,需手动
ssh + psql应用。
5.4 接口
POST /deck/create 生成整套(异步,返回 taskId)
POST /deck/get 取整套
POST /deck/update_slide 保存单页(整页 layers)
POST /deck/add_slide 新增页
POST /deck/delete_slide 删除页
POST /deck/reorder_slides 页排序
POST /deck/apply_theme 换风格(只换 themeId,前端重渲染)
POST /deck/relayout_slide 单页换版式(重跑排版引擎)
POST /deck/export { deckId, format: pptx | pdf | png }
GET /deck/themes 风格列表
5.5 导出映射
| 内部模型 | PPTX |
|---|---|
box.x/y/w/h(1920 设计 px) |
Inches(px / 144) —— 1px = 1/144 in,1920px = 13.333in |
sizeStep → scale.size-steps[step] |
Pt(px / 2) |
kind: text |
add_textbox,关闭 autofit,word_wrap = True |
kind: image |
add_picture |
kind: shape |
add_shape(圆角矩形 / 椭圆 / 直线) |
colorToken |
查主题表 → RGBColor |
fontToken |
写字体名(白名单)。同时设 latin + eastasian,主题字体也一并设 |
| 复杂视觉层 | 降级为图片层 |
两个 python-pptx 的坑
中文字体要写三处。
run.font.name只写<a:latin>,中文会退回主题默认字体。必须额外写<a:ea>/<a:cs>(东亚字体是<a:ea>),且顺序是 latin → ea → cs。空
<a:effectLst/>挡不住 LibreOffice 的投影。 python-pptx 的add_shape/add_connector