Files
html2pptx/docs/architecture.md
T
李进 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

204 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# html2pptx 通用转化架构
## 1. 目标 Schema 定义
### 顶层结构
```json
{
"presentation": {
"layout": "CUSTOM",
"slideWidth": 24.18, // 英寸
"slideHeight": 17.08 // 英寸
},
"slides": [
{
"background": { "color": "1A1A1A" }, // 可选
"objects": [
// text / shape / image / table / chart
]
}
]
}
```
### text 对象
```json
{
"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 对象
```json
{
"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 对象
```json
{
"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 样本验证通用性