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 注入(换肤不改变排版算法)

推荐流水线

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 测高修正。
// 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。
  • 稳定性优先: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 → :root CSS 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 扩覆盖。
© 工程复现手册(HTML)。你可以直接保存本页为 manual.html,在本地打开阅读与分享。