Files
李进 f7307a0891 refactor: web-to-pixso → web-to-ppt 全面改名
- 目录 web-to-pixso/ → web-to-ppt/
- 所有代码引用:PIXSO → WEB_TO_PPT, webToPixso → webToPPT
- 数据格式:pixso-design-capture → web-to-ppt-capture
- CSS 选择器:__web_to_pixso_ → __web_to_ppt_
- README 重写,移除 Pixso 相关描述
- docs/web-to-pixso-mapping.md → docs/web-to-ppt-mapping.md
2026-07-27 14:12:44 +08:00

5.5 KiB
Raw Permalink Blame History

html2pptx 通用转化架构

1. 目标 Schema 定义

顶层结构

{
  "presentation": {
    "layout": "CUSTOM",
    "slideWidth": 24.18,    // 英寸
    "slideHeight": 17.08    // 英寸
  },
  "slides": [
    {
      "background": { "color": "1A1A1A" },  // 可选
      "objects": [
        // text / shape / image / table / chart
      ]
    }
  ]
}

text 对象

{
  "type": "text",
  "text": "文字内容",
  "options": {
    "x": 1.463,          // 英寸(左上角 x
    "y": 1.039,          // 英寸(左上角 y
    "w": 2.282,          // 英寸(宽度)
    "h": 0.25,           // 英寸(高度)
    "fontSize": 14,      // pt
    "fontFace": "Arial", // 字体名
    "color": "000000",   // 6位hex
    "bold": false,       // 可选
    "italic": false,     // 可选
    "align": "left",     // left/center/right/justify
    "underline": false,  // 可选
    "charSpacing": 0     // 可选,字符间距
  }
}

shape 对象

{
  "type": "shape",
  "shapeName": "rect",   // rect/roundRect/ellipse
  "options": {
    "x": 0.83,
    "y": 3.07,
    "w": 17.78,
    "h": 3.12,
    "fill": { "color": "ffffff" },
    "line": { "type": "none" },          // 可选
    "shadow": {                          // 可选
      "type": "outer",
      "blur": 15,
      "offset": 5,
      "color": "999999",
      "opacity": 0.5
    },
    "rectRadius": 0.1,                  // 可选,英寸
    "opacity": 1.0                      // 可选
  }
}

image 对象

{
  "type": "image",
  "options": {
    "x": 0,
    "y": 0,
    "w": 10,
    "h": 5,
    "data": "base64..."   // 或 "path": "file.png"
  }
}

2. 通用转化逻辑

核心原则

  • 不靠命名,靠结构:slide 容器 = 包含内容子节点的 FRAME
  • 不靠猜测,靠数据:所有属性从 JSON 字段直接读取
  • 不硬编码规则:通过配置或自动推断

2.1 自动识别 Slide 容器

输入:web-to-ppt JSON 的 nodes 树
输出:slide 容器列表

策略:
1. 遍历 nodes 树,找 FRAME 类型节点
2. 判断条件(满足任一):
   a) name 匹配已知模式:slide-N / page / slide+空格 / slide+横杠
   b) 结构特征:
      - children 数量 > 0
      - 面积接近 canvas 面积(> 50% canvas 面积)
      - 不是 BODY / HTML / HEAD 等结构性节点
3. 排除条件:
   - layerGroup === 'comparison'(截图底图)
   - 没有 children
   - 面积太小(< 100px × 100px

2.2 收集子节点(递归)

collectSlideChildren(node, slideX, slideY, parentW, depth):
  对于每个子节点:
    - 计算相对坐标:rect.x - slideX, rect.y - slideY
    - 记录 parentW(父容器宽度)
    - 记录 depth(层级深度)
    - 跳过 RECTANGLE 类型(截图底图)
    - 跳过 layerGroup === 'comparison'
    - 递归处理 children

2.3 收集游离节点

collectOrphans(root, slideIds):
  slideIds = 所有 slide 容器的 id 集合
  
  遍历 root 树:
    - 如果节点 id 在 slideIds 中 → 跳过整个子树
    - 否则 → 收集该节点,递归 children

2.4 映射 TEXT 节点

输入:TEXT 类型节点
输出:text 对象

映射规则:
  fontSize    = parseFloat(styles.fontSize) || 14
  fontFace    = styles.fontFamily.split(',')[0].trim()
  color       = rgbaToHex(styles.color) || '000000'
  bold        = parseInt(styles.fontWeight) >= 700
  italic      = styles.fontStyle === 'italic'
  align       = styles.textAlign(非 'start' 时)
  underline   = styles.textDecorationLine 包含 'underline'
  charSpacing = styles.letterSpacing 转换
  
  宽度计算:
    contentW = slide 内容宽度(从子节点 x 范围推算)
    textW = parentW || rect.w
    if (depth === 1) textW = contentW
    if (textW > contentW) textW = contentW
    if (textW < 108) textW = 108  // 最小 1.5in
    if (x + textW > contentW) textW = contentW - x

2.5 映射 FRAME 节点 → shape

输入:FRAME 类型节点(有背景色)
输出:shape 对象

映射规则:
  fillColor  = rgbaToHex(styles.backgroundColor)
  渐变背景   = backgroundImage 提取起始色
  圆角       = borderTopLeftRadius 转换
  边框       = 四边都有 border 时加 line
  阴影       = boxShadow 解析
  单边 border = 独立线条 shape
  默认 line  = { type: 'none' }(去掉 pptxgenjs 默认边框)

2.6 背景色推断

优先级:
1. slide 容器的 styles.backgroundColor(非透明时)
2. canvas.backgroundColor(全局兜底)
3. 从 slide name 推断(dark → #1A1A1A, light → #FAFAFA
4. 从文字颜色推断(浅色文字 → 深色背景,深色文字 → 浅色背景)

3. 当前硬编码 → 通用化改造清单

硬编码 当前逻辑 通用化方案
slide 匹配 slide-N / page / slide 结构识别 + 已知模式列表
游离节点跳过 startsWith('slide-') / startsWith('slide ') 用 slide id 集合判断
背景色 dark/light 关键词 优先取 styles,兜底取 canvas
TEXT 宽度 depth === 1 判断顶层 用 parentW 与 contentW 的关系判断
边框 只处理 borderTop 四边独立处理

4. 下一步

  1. 重构 findSlides() — 支持结构识别
  2. 重构 collectOrphans() — 用 id 集合判断
  3. 重构背景色逻辑 — 多级兜底
  4. 补全 styles 映射 — opacity / lineHeight / letterSpacing / textDecoration
  5. 测试多个 HTML 样本验证通用性