NotebookLM风格:Slide Deck 生成器(HTML)工程落地指导手册
原创
灵阙教研团队
B 基础 进阶 |
约 11 分钟阅读
更新于 2026-01-13 AI 导读
NotebookLM风格:Slide Deck 生成器(HTML)工程落地指导手册 目标:复现“任意风格适配 + 排版稳定出色”的底层工程逻辑,产出可渲染为 HTML(并可导出 PDF / PPTX)。 核心策略:风格(Tokens) 与 排版(模板+约束引擎) 解耦,靠 确定性布局 + 验证与修复回路 保证稳定观感。 目录 1. 目标与边界 2. 总体架构与数据流 3. 仓库结构建议 4....
NotebookLM风格:Slide Deck 生成器(HTML)工程落地指导手册
目标:复现“任意风格适配 + 排版稳定出色”的底层工程逻辑,产出可渲染为 HTML(并可导出 PDF / PPTX)。
核心策略:风格(Tokens) 与 排版(模板+约束引擎) 解耦,靠 确定性布局 + 验证与修复回路 保证稳定观感。
1) 目标与边界
要实现(Must)
- 风格无穷:通过 Theme Tokens(颜色/字体/间距/形状/插画风格)快速换肤
- 排版稳定:文字不溢出、不重叠,对齐/留白一致
- 两种密度:Presenter(少字更演讲)/ Detailed(信息更完整)
- 可复用:同一套 IR 可以输出 HTML、PDF、PPTX
暂不追求(Not now)
- 完全自由的“无模板”生成(极易溢出、不可控)
- 把关键文字画进图片(不可检索、不可编辑、跨语言更崩)
- 100% 复刻某个闭源产品内部实现(我们做的是工程等价方案)
成败关键
| 能力 | 决定因素 | 工程抓手 |
|---|---|---|
| “适应任何风格” | 风格注入是否可执行 | Tokens +(可选)brandbook/样例 deck 抽取 tokens |
| “排版非常出色” | 是否由确定性系统掌控像素级布局 | 模板库 + 文字测量 + 约束布局 + 回退策略 |
| “任何内容都能做” | 内容结构化程度 + 模板覆盖面 | Slide IR(意图/组件树)+ 30~80 模板覆盖常见意图 |
2) 总体架构与数据流
原则 1LLM 不直接画像素级布局,只产出结构化计划与候选内容
原则 2布局由确定性引擎完成(可测量、可验证、可修复)
原则 3风格由 tokens 注入(换肤不改变排版算法)
原则 2布局由确定性引擎完成(可测量、可验证、可修复)
原则 3风格由 tokens 注入(换肤不改变排版算法)
推荐流水线
Inputs (Sources + Prompt + Brandbook/Sample Deck)
|
v
[1] Content Planner ----> Slide Outline (IR v0)
|
v
[2] Style Builder ----> Theme Tokens
|
v
[3] Slide Composer ----> Component Tree per slide (IR v1)
|
v
[4] Layout Engine ----> BBoxes + font sizes + line breaks (Layout IR)
|
v
[5] Renderer ----> HTML / PDF / PPTX
|
v
[6] Validators ----> Issues
|
v
Repair Loop (rules + LLM rewrite + template swap + split slides)
关键中间产物
- Slide IR:每页意图、组件树、文本块、数据、媒体占位(不含像素)
- Theme Tokens:颜色/字体/间距等可执行风格参数
- Layout IR:bbox(x,y,w,h)、字号、行高、换行结果(最终可渲染)
3) 仓库结构建议
deckgen/
packages/
core-ir/ # IR types + schema + validators
planner/ # Content Planner (LLM prompts + postprocess)
style/ # tokens + brandbook/sample extractor
templates/ # template DSL + template library
layout/ # deterministic layout engine
renderer-html/ # HTML renderer (Layout IR -> DOM/CSS)
exporter-pdf/ # Playwright/Puppeteer export
exporter-pptx/ # PptxGenJS export
cli/ # deckgen build --input ... --out ...
apps/
studio-web/ # web UI: preview + edit + re-run repair
tools/
visual-regression/ # screenshot diff
perf/ # benchmarks
docs/
manual.html # 本手册
工程建议:先把 “IR → Layout → HTML” 跑通并稳定,再接 LLM、图片、导出。
4) 核心数据模型(IR / Tokens / Templates)
4.1 Slide IR(推荐字段)
{
"deck": {
"meta": { "title": "string", "language": "zh-CN", "mode": "presenter|detailed" },
"themeRef": "theme.default",
"slides": [
{
"id": "s1",
"intent": "cover|section|concept|comparison|process|timeline|data|quote|summary",
"title": "string",
"subtitle": "string?",
"bullets": ["string", "..."],
"notes": "string?",
"assets": [
{ "type": "image|icon|chart", "ref": "assetId", "styleHint": "flat|handdrawn|..." }
],
"data": { "type":"table|series", "payload": {} },
"constraints": {
"maxBullets": 5,
"tone": "formal|playful|...",
"emphasis": ["key phrases..."]
}
}
]
}
}
为什么要 intent? intent 决定模板选择与布局策略,是“排版稳定”的入口。
4.2 Theme Tokens(风格可执行化)
{
"themeId": "theme.corpA",
"palette": {
"bg": "#0B1220",
"text": "#E6EDF3",
"primary": "#7AA2FF",
"secondary": "#34D399",
"accent": "#FBBF24",
"surface": "#101826",
"line": "#22324A"
},
"typography": {
"titleFont": "Inter",
"bodyFont": "Inter",
"monoFont": "JetBrains Mono",
"scale": { "h1": 44, "h2": 32, "h3": 24, "body": 18, "small": 14 },
"weight": { "title": 700, "body": 450 }
},
"spacing": { "grid": 8, "safeMargin": 48, "gap": 16, "lineHeight": 1.25 },
"shape": { "radius": 16, "stroke": 1, "shadow": "soft" },
"imagery": { "style": "flat", "grain": 0.1, "bgPattern": "none" },
"charts": { "axis": "minimal", "labels": "compact" }
}
4.3 Template DSL(结构+约束,不写死风格)
{
"templateId": "concept.2col.imageRight",
"intent": "concept",
"grid": { "cols": 12, "rows": 12, "safe": 48 },
"slots": [
{ "name": "title", "type": "text", "area": [1,1,7,2], "styleRole": "h2" },
{ "name": "bullets", "type": "list", "area": [1,3,7,8], "styleRole": "body", "maxLines": 10 },
{ "name": "visual", "type": "media", "area": [8,2,11,10], "fit": "cover", "minVisible": 0.6 }
],
"constraints": {
"align": "baselineGrid",
"minGap": 16,
"noOverlap": true
}
}
模板数量建议:20 个跑 MVP;稳定后扩到 30~80 覆盖主流意图。
5) 模块落地指南
5.1 Content Planner(内容规划)
- 输入:Sources(片段/引用/表格)+ 用户 prompt(受众/语气/长度/风格)
- 输出:Slide IR v0(按 intent 分页,严格控制每页信息量)
- 强制规则:每页 bullet 句长上限、数量上限;必要时拆页
// 典型 Planner 输出约束(伪码)
if mode == "presenter":
maxBullets = 4; maxWordsPerBullet = 12
else:
maxBullets = 6; maxWordsPerBullet = 18
5.2 Style Builder(风格构建)
- 最小可行:prompt → tokens 映射(比如 “极简/科技/复古/手绘” 到预置 token 集)
- 进阶:brandbook(文本)解析出色值/字体/间距;sample deck 统计常用版式密度
不要把风格写成一句形容词。必须落到 tokens 才能“稳定复用”。
5.3 Slide Composer(组件树组装)
- 根据 intent 从模板池选 3~5 个候选模板(score:内容类型匹配、媒体可用、密度合适)
- 把 IR 内容填充到 slots:title/bullets/visual/chart/quote...
- 输出 IR v1:明确每个 slot 内容来源与优先级(后续可裁剪)
5.4 Layout Engine(确定性排版)
- 输入:Template + Theme Tokens + IR 内容
- 输出:Layout IR(每个元素 bbox、字号、换行、裁剪)
- 必要能力:文字测量、换行、溢出回退(缩字/删点/换模板/拆页)
5.5 Renderer(HTML 渲染)
- Layout IR → DOM:每个元素绝对定位或 CSS grid 定位
- tokens → CSS variables:统一主题与组件样式
- 确保可截图(导出 PDF)与可编辑(后续支持拖拽微调)
6) 确定性排版引擎细节(最关键)
6.1 网格与安全边距
- 画布:16:9(例如 1920x1080)或 4:3,统一抽象为“单位坐标系”
- safe margin:默认 48px(或 tokens.safeMargin)
- baseline grid:8px(或 tokens.spacing.grid)吸附,增强“整洁感”
6.2 文字测量(必须做)
浏览器端:
用 CanvasRenderingContext2D.measureText + 二分法找最大字号,结合实际 DOM 测高修正。
用 CanvasRenderingContext2D.measureText + 二分法找最大字号,结合实际 DOM 测高修正。
// measureText(简单版) + 二分字号
function fitFontSize(text, fontFamily, maxW, maxH, min, max){
let lo=min, hi=max;
while(lo<=hi){
const mid=(lo+hi)>>1;
const {w,h}=measureBlock(text,fontFamily,mid,maxW);
if(w<=maxW && h<=maxH) lo=mid+1; else hi=mid-1;
}
return hi;
}
服务端(Node):
可用 node-canvas + opentype.js 获取 font metrics;或直接用 headless Chromium 渲染后读取 bbox。
可用 node-canvas + opentype.js 获取 font metrics;或直接用 headless Chromium 渲染后读取 bbox。
- 稳定性优先:Headless 渲染测量最准
- 性能优先:缓存测量结果(key=font+size+width+text hash)
6.3 换行策略
- MVP:贪心换行(按词/按字),配合 CJK 断行规则
- 进阶:Knuth-Plass(类似 TeX)可减少参差不齐的行尾,但成本更高
6.4 溢出回退(按优先级执行)
// 建议的回退策略(从“最不伤观感”到“结构性改变”)
1) Rewrite: 让 LLM 缩短(减少形容词/去重复/合并要点)
2) Downscale: 降字号(到下限,比如 body >= 14)
3) Trim: 减 bullet 数(保留 top-k,剩余移到下一页)
4) Swap template: 选更“文字友好”的模板(纯文/两栏)
5) Split slide: 拆成 2 页(Detailed 更适用)
6) Fallback: 强制 summary 模板(兜底结论页)
反模式:遇到溢出就随机缩小到很小字号,页面会“看起来像事故现场”。必须有下限与拆页策略。
7) Validators + Repair Loop(专业感分水岭)
7.1 必做校验项(可自动化)
| 规则 | 判定 | 修复动作 |
|---|---|---|
| No Overflow | 任何元素 bbox 不得超出 safe area | 降字号 → 改文案 → 换模板 → 拆页 |
| No Overlap | 关键元素 bbox 不相交(允许装饰层覆盖) | 挪位/改布局 → 换模板 |
| Contrast | 文字与背景对比度 ≥ 阈值 | 换文字色/加底板/调背景亮度 |
| Alignment | 关键边对齐网格(误差 <= 1~2px) | 吸附到 grid,统一 gap |
| Density | 文字面积占比/行数上限(Presenter 更严格) | 删点/拆页/换模板 |
| Consistency | 同层级字号/字重一致,tokens 白名单 | 统一 styleRole,重算样式 |
7.2 Repair Loop 设计
- 输入:issues(结构化) + 原 IR + tokens + 模板候选
- 规则修复优先(快且确定),LLM 只做“改文案/拆页建议/重组要点”
- 每轮修复后必须重新 Layout + Validate,设定最大迭代次数(如 3~5)
{
"issues":[
{"type":"overflow","nodeId":"bullets","excessPx":84,"suggest":"rewrite_or_split"},
{"type":"contrast","nodeId":"title","ratio":2.1,"suggest":"add_backplate"}
],
"actions":[
{"type":"llm_rewrite","nodeId":"bullets","target":"shorter"},
{"type":"add_backplate","nodeId":"title","opacity":0.28}
]
}
8) HTML 渲染与导出(PDF/PPTX)
8.1 HTML 渲染策略
- 每页一个
<section class="slide">,固定宽高(如 1280x720 / 1920x1080) - tokens →
:rootCSS variables;styleRole → class(h1/body/caption) - 布局:建议绝对定位(bbox 渲染最直接),也可用 CSS Grid 但最终仍需 bbox 对齐
<section class="slide" style="--w:1920px;--h:1080px">
<div class="node title" style="left:96px;top:96px;width:1200px;height:120px;font-size:44px">...</div>
<ul class="node bullets" style="left:96px;top:240px;width:920px;height:720px">...</ul>
<img class="node visual" style="left:1100px;top:180px;width:720px;height:780px;object-fit:cover" />
</section>
8.2 导出 PDF(推荐)
- 用 Playwright / Puppeteer 打开渲染后的 HTML,逐页截图或直接打印为 PDF
- 要保证字体加载完成:
document.fonts.ready后再截图
// 伪码:导出 PDF
await page.goto("file:///deck.html");
await page.evaluate(() => document.fonts.ready);
await page.pdf({ format:"A4", printBackground:true });
8.3 导出 PPTX(可选)
- 用 PptxGenJS:把 Layout IR 的 bbox 转成 pptx 坐标(注意单位换算)
- 文字用 pptx text box(可编辑),背景/插画作为图片
建议:PPTX 输出的首要目标是“可编辑”,所以不要把正文画进图片里。
9) 测试、质量与性能
9.1 视觉回归测试(强烈建议)
- 固定 seeds / 固定 tokens / 固定 templates:渲染截图 → 像素 diff(阈值)
- 每次改布局引擎都跑 golden cases(覆盖 CJK/长标题/多 bullet/无图)
9.2 属性测试(Property-based)
- 随机生成文本长度、bullet 数、图占比,断言:不溢出、不重叠
- 对修复回路:最多 N 次迭代必须收敛或优雅兜底(拆页/summary)
9.3 性能优化
- 文字测量缓存:key = font + size + width + textHash
- 模板选择先粗筛(intent 匹配/媒体可用)再精排
- 导出使用队列 + 复用浏览器实例(避免冷启动)
10) 多语言 / CJK / RTL(常见坑)
- CJK:按字断行优先,标点挤压规则(避免行首标点)
- 英文:按词断行,支持连字符(hyphenation)可选
- RTL:布局镜像(slots area 镜像),文本方向
dir="rtl" - 字体:为每种语言配置 fallback 字体栈,避免字形缺失导致测量偏差
重要:不同字体的 metrics 差异会让“测量 vs 实际渲染”偏离,务必在真实渲染环境测量或校准。
11) 安全与合规
- Sources 引用:保留来源片段 id,支持回溯(生成内容可解释)
- 内容注入:渲染 HTML 时对文本做转义,避免 XSS
- 外部图片:下载与缓存,避免导出时网络抖动;并做内容类型校验
- 隐私:对用户上传文档做最小化持久化与访问控制
12) MVP 里程碑与清单(按最短路径)
MVP 第 1 阶段:稳定排版(不接 LLM)
- 定义 IR + tokens + 20 个模板
- Layout 引擎:文字测量 + 回退策略
- HTML renderer:可预览、可导出 PDF
- Validators:overflow/overlap/alignment
MVP 第 2 阶段:接 LLM 生成内容
- Planner:按 intent 分页 + 密度控制
- Repair loop:溢出时 rewrite/split
- 预置风格:prompt → tokens 映射
第 3 阶段:品牌化与素材
- brandbook → tokens 抽取
- 插画/背景生成接口(不画主文字)
- 图表:SVG 渲染(可编辑)
第 4 阶段:PPTX 输出
- bbox → pptx 坐标映射
- 文本保持为 text box
- 模板与主题映射(shape/颜色/字体)
附录:示例 Schema / 模板 DSL / 校验规则
A) 简化 JSON Schema(片段示意)
{
"$id":"deck.schema.json",
"type":"object",
"properties":{
"deck":{
"type":"object",
"properties":{
"meta":{"type":"object"},
"themeRef":{"type":"string"},
"slides":{
"type":"array",
"items":{
"type":"object",
"required":["id","intent","title"],
"properties":{
"id":{"type":"string"},
"intent":{"enum":["cover","section","concept","comparison","process","timeline","data","quote","summary"]},
"title":{"type":"string"},
"bullets":{"type":"array","items":{"type":"string"}}
}
}
}
},
"required":["meta","themeRef","slides"]
}
},
"required":["deck"]
}
B) 校验规则(可配置化)
{
"rules":{
"overflow":{"enabled":true,"safeMargin":48},
"overlap":{"enabled":true,"allowDecorativeOverlap":true},
"contrast":{"enabled":true,"minRatio":4.0},
"density":{"enabled":true,"presenterMaxTextArea":0.28,"detailedMaxTextArea":0.42},
"alignment":{"enabled":true,"grid":8,"tolerance":2}
}
}
C) Template 覆盖清单(建议从这些开始)
- cover: 3
- section: 3
- concept: 6
- comparison: 4
- process: 4
- timeline: 3
- data: 4
- quote: 2
- summary: 3
落地提醒:模板越少越要“高质量”,宁可少而精,再靠 repair loop 扩覆盖。