DESIGN.md@packages/@openmaic/importer · git:20260624.7cb1291 · 2026-06-24 · sha256 e8b98723c68740b4

DESIGN.md@packages/@openmaic/importer git:20260624.7cb1291A

Immutable. This exact content is served forever at /api/v1/blob/e8b98723c68740b4.

# DESIGN

`maic-importer`:将 `.pptx` 文件解析为结构化 JSON,供 PPTist 等下游渲染器消费。

## 解析管线

```
.pptx (ArrayBuffer)
  ↓  parser/ZipParser ── 解压 zip,按用途分类
PptxFiles
  ↓  model/* ── XML → 结构化模型(位置、大小、层级)
PresentationData
  ↓  serializer/* ── 模型 + 主题/模板上下文 → JSON 元素
Element[]
  ↓  adapter/toPptxtojson ── 组装最终输出
Output { slides, themeColors, size }
```

入口:`src/index.ts` → `parse(buffer, options?)`

## 分层职责

| 层 | 目录 | 做什么 | 不做什么 |
|---|---|---|---|
| **parser** | `src/parser/` | zip 解压、XML 解析(SafeXmlNode)、rels 映射、单位换算 | 不解析 OOXML 业务语义 |
| **model** | `src/model/` | 解析几何与结构:位置、大小、旋转、占位符、节点类型 | 不解析视觉样式(颜色、字体、填充) |
| **serializer** | `src/serializer/` | 结合 theme/master/layout 上下文,把模型转成 JSON 元素 | 不直接读 zip |
| **adapter** | `src/adapter/` | 定义对外 JSON 类型,组装最终 Output | 不写业务逻辑 |
| **shapes** | `src/shapes/` | 生成 SVG path 字符串(200+ preset + 自定义几何) | 不决定填充/边框 |
| **utils** | `src/utils/` | 通用工具(颜色变换、媒体格式、EMF 解析、EQ 公式等) | 不引用业务类型 |

依赖方向:`adapter → serializer → model → parser`,单向。`shapes` 和 `utils` 是底层工具。

## 目录结构

```
src/
├── index.ts                    # 入口:parse() + 类型导出
├── adapter/
│   ├── types.ts                # ★ 对外 JSON 类型定义(改它 = 改协议)
│   └── toPptxtojson.ts         # 组装 Output
├── parser/
│   ├── ZipParser.ts            # .pptx → PptxFiles
│   ├── XmlParser.ts            # SafeXmlNode(null-safe DOM 包装)
│   ├── RelParser.ts            # .rels 关系映射
│   └── units.ts                # EMU / pt / px / 角度换算
├── model/
│   ├── Presentation.ts         # 组装 theme → master → layout → slide 链
│   ├── Theme.ts / Master.ts / Layout.ts / Slide.ts
│   └── nodes/                  # 各节点类型解析器
│       ├── BaseNode.ts         # 共用属性(位置、大小、旋转、xmlOrder)
│       ├── ShapeNode.ts        # sp / cxnSp
│       ├── PicNode.ts          # pic
│       ├── TableNode.ts / ChartNode.ts / GroupNode.ts / MathNode.ts
├── serializer/
│   ├── RenderContext.ts        # 每页渲染上下文(slide → layout → master → theme)
│   ├── slideSerializer.ts      # 编排:背景 → master 装饰 → layout 装饰 → slide 元素
│   ├── shapeSerializer.ts      # ★ Shape/Text 判定、preset 路径、自适应
│   ├── textSerializer.ts       # TextBody → HTML 富文本
│   ├── tableSerializer.ts      # 表格样式级联
│   ├── chartSerializer.ts      # 图表数据提取
│   ├── imageSerializer.ts      # 图片/视频/音频
│   ├── mathSerializer.ts       # OMML → LaTeX
│   ├── groupSerializer.ts      # ★ 坐标空间缩放 + flip/rotation 烘焙
│   ├── StyleResolver.ts        # 颜色/填充 → CSS
│   ├── backgroundSerializer.ts # 背景填充
│   └── borderMapper.ts         # 线型 → dasharray
├── shapes/
│   ├── presets.ts              # 200+ OOXML preset 几何
│   ├── customGeometry.ts       # 自定义几何 → SVG path
│   └── shapeArc.ts             # 弧形计算
├── utils/
│   ├── color.ts                # OOXML 颜色变换全套
│   ├── media.ts                # MIME / 路径
│   ├── mediaWebConvert.ts      # TIFF/EMF/JXR → PNG
│   ├── emfParser.ts            # EMF 内嵌提取
│   ├── eqFieldParser.ts        # Word EQ 域公式 → LaTeX
│   ├── rgbaToPng.ts            # RGBA → PNG 编码
│   └── urlSafety.ts            # 外链白名单
├── export/
│   └── serializePresentation.ts  # 调试用:扁平化输出
└── types/
    └── vendor-shims.d.ts
```

★ 标记的是定位问题时最常修改的文件。

## 核心设计点

### 模型层不感知样式
`model/*` 只解析"是什么、在哪里、多大"。视觉样式(颜色、字体、填充)留给 serializer 层,因为需要 theme/master/layout 的级联解析。

### Serializer 是纯映射
`*ToElement(node, ctx, order)` 输入模型 + 上下文,输出 JSON 元素,无副作用。定位 bug 时只需怀疑对应的 serializer 文件。

### Group 坐标烘焙
`groupSerializer.ts` 处理两件事:
1. **chOff/chExt 缩放**:子元素坐标从 group 内部坐标系映射到外层坐标
2. **flip/rotation 烘焙**:group 的变换折算进子元素,输出的 group 始终中性(`rotate:0`, `isFlipH/V:false`)

### 元素层级(order)
`layoutElements`(master + layout 装饰)和 `elements`(slide 内容)分开输出。`layoutElements` 内部,layout 元素的 order 会加偏移量,保证始终在 master 元素之上。每个元素的 `order` 来自 `xmlOrder`(文档深度优先遍历索引)。

### 单位约定
- 对外 JSON 一律 **pt**(`left/top/width/height`)
- 颜色一律 `#RRGGBB`,角度一律 deg
- 内部 EMU 在 model 层转 px,adapter 层 px → pt

## 输出结构概览

完整类型定义见 `src/adapter/types.ts`。

| `type` | 关键字段 |
|---|---|
| `text` | `content`(HTML), `vAlign`, `isVertical`, `autoFit` |
| `shape` | `shapType`, `path`, `keypoints`, `content`, `vAlign` |
| `image` | `src`, `geom`, `rect`(裁切), `filters` |
| `table` | `data[][]`, `borders`, `rowHeights`, `colWidths` |
| `chart` | `chartType`, `data`, `colors` |
| `group` | `elements[]`(坐标已转换,变换已烘焙) |
| `math` | `latex`, `picBase64`, `text` |
| `video`/`audio` | `src`, `blob` |

## 参考实现

- `src1/`:原版 JS 实现,**只读不改**,作为参考

## 脚本

| 命令 | 用途 |
|---|---|
| `npx tsx scripts/transvert.ts <file.pptx> [out.json]` | 用本库解析(开发主力,直接跑源码无需构建) |
| `npx tsx scripts/transvert.js <file.pptx> [out.json]` | 用 pptxtojson 原版解析 |
| `node scripts/extract-pptx-structure.js <file.pptx> [outDir]` | 解压 .pptx 查看源 XML |
| `pnpm build` | Rollup 打包 + 生成 .d.ts → dist/ |
| `pnpm lint` | ESLint 检查 |