# PPTX 生成：文本、形状、图片、表格与动画

从 `fsys.openXml` 0.6.0 起，可不启动 Office/WPS 生成 `.pptx`。

**直接导入 `fsys.openXml.pptx.builder` 是纯生成路线**：不加载 ZIP 读取器、MSXML、.NET 或技能包。ZIP 写入复用公共 `zlib.zip.writeData`，不为每个部件创建临时文件。主题与备注母版编译内嵌，运行时不需要参考 PPTX。

## 最小示例

```aardio
import fsys.openXml.pptx.builder;
var slides = [{
    background = "F4F7FC";
    notes = "讲解备注";
    transition = {effect="fade";duration=0.7};
    shapes = [{
        kind="text";name="Title";
        x=60;y=60;width=840;height=100;
        text="由 aardio 生成的 PPTX";
        fontSize=36;bold=true;color="18324F";
        animation={effect="fade";trigger="click";duration=0.6};
    }];
}];
var path,err = fsys.openXml.pptx.builder.write("/native.pptx",slides);
if(!path) return null,err;
return path;
```

`write(path,slides,options)` 返回完整路径，失败 `null,err`，**默认不覆盖**。只有明确指定 `{overwrite=true}` 才覆盖旧目标。`fsys.openXml.pptx.write` 与技能 `writePptx` 为便捷转发；需要最小依赖时用上面的直接导入。

## 0.12.1：表格边距与垂直对齐兼容修复

表格 `margin`、`valign` 的 API 与默认值不变。单元格只通过 `a:tcPr` 写入四边边距和垂直对齐，内部 `a:bodyPr` 省略 `lIns/rIns/tIns/bIns/anchor`，保留 `wrap="square"` 和 `noAutofit`。显式写零边距/顶部对齐并不等于省略：WPS 的某些版本会用这些值覆盖 `tcPr`。

修复仅作用于新生成的表格；普通文本框、形状文字、占位符及备注保持原输出，预检仍只扣一次单元格边距。不要改用空格、段落缩进、缩小字号或在两层重复写边距补偿。旧文稿的编辑保留机制不自动清除这些属性；可重新运行 `~/examples/File/OpenXML/pptx-data.aardio` 生成修正版。

用户提供的 WPS Build 12.1.0.16929 对照验证表明：同时省略五属性可恢复 80 个物理格的 9pt 边距及居中，实读字号仍为18pt/15pt。本机 PowerPoint 16 的修前/修后七页同尺寸PNG逐字节相同，实际边距、文本边界、行高与合并格尺寸保持，回存重开也正常。此结论不外推为全部应用版本的字号/排版绝对一致。

## 0.12.0：二维原生图表

`kind="chart";chart={type,categories,series}` 支持二维柱状/条形/折线/饼图，每图带独立内嵌XLSX，引用与缓存一致。详见 [二维原生图表](../chart/index.html)。完整演示：`~/examples/File/OpenXML/pptx-charts.aardio`。缺值使用null，与0区分；尾部null通过数组元表length保留。`check`不模拟图表内部标签排版。

## 0.11.0：合并单元格与数据模板

表格 `merges=[{row,column,rowSpan,colSpan}]` 支持横向/纵向/矩形合并，行列从1开始；保留完整物理 `rows`，覆盖格必须为空。生成与预检共用合并拓扑与总宽高，详见 [模板与表格数据](../editor/data.html)。完整演示 `~/examples/File/OpenXML/pptx-data.aardio` 还展示 editor 复制/删页与保留格式填值。

## 0.10.0：页码、超链接、编号与外观

支持 `options.slideNumber` 自动页码、运行 `field="slideNumber"`、文字/形状/图片 `hyperlink`、段落 `numbering`、外阴影 `shadow` 与圆角矩形 `cornerRadius`。详见 [页码、超链接、编号与外观细节](details.html)。完整五页交互演示：`~/examples/File/OpenXML/pptx-details.aardio`。

## 0.9.0：标题、标准版式与放映

`slide.layout` 可为 `blank`（默认）、`title`、`titleContent`、`twoContent`。标准版式提供真正的标题/正文占位符，并非只有外观的文本框；版式部件、幻灯片及预检共用默认几何。只生成显式提供的 shapes，不自动增加未提供的内容。

- `placeholder="title"`：标题（index=0；标题页为 ctrTitle）。
- `placeholder="subtitle"`：副标题（index=1），用于 title 或显式 blank 页。
- `placeholder="body"`：正文（默认 index=1）；第二栏写 `{type="body";index=2}`。
- 仅 `kind="text"` 可做占位符；同页占位符索引必须唯一。省略 x/y/width/height/字号时采用版式默认值，显式字段覆盖。此处 body 是文本占位符，不是通用图表/图片插入容器。
- `animation.byParagraph=true` 按非空真实段落展开，软换行不拆段。首次用 trigger/delay；后续用 paragraphTrigger（click/with/after，默认 click）和 paragraphDelay（默认0秒）。`paragraph=N` 可仅播放某段（从1开始），不能与 byParagraph 同用。不按缩进层级合组，仅支持 entrance/exit 的五种已支持效果。
- `transition.effect="morph"` 支持 option=byObject/byWord/byChar（默认 byObject），同时写入 p14/标准淡出回退。相邻页同一对象可用相同的 `!!` 前缀名称辅助匹配。旧应用的淡出回退不等同 Morph。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{
    layout="titleContent";
    shapes=[
        {name="Title";placeholder="title";text="按段落讲解"},
        {name="Body";placeholder="body";text='第一点\r\n第二点\r\n第三点';
            animation={effect="fade";byParagraph=true;duration=0.45}}
    ];
}];
return fsys.openXml.pptx.builder.write("/paragraphs.pptx",slides);
```

完整五页示例：`~/examples/File/OpenXML/pptx-show.aardio`。第2页点击三次，第3→4页观察 Morph，第5页两栏自动逐段播放。本机 PowerPoint 16 已检查标题 API、段落点击和连续中间帧；用户于2026年10月8日在两台电脑的WPS中验证该演示播放与排版正常（未提供版本号）；旧 Office 尚未验收。基础版式不是用户母版导入器。

## 坐标与颜色

- 所有位置、尺寸、字号、边距与线宽使用 **point**，1 点转换为 12700 EMU。
- 幻灯片默认 `width=960,height=540`，即 16:9；宽高范围为 72～4032 点。
- `x,y` 为左上角，`width,height` 为尺寸；允许形状放到页面外，但不会替你修正裁切。
- 颜色只接受 `"RRGGBB"` 或 `"#RRGGBB"`。**不接受 aardio/Office 的 COLORREF 数值**，以免红蓝颠倒。`fill=false` 或 `lineColor=false` 表示不填充。
- `rotation` 使用角度，正数顺时针；自动归一化到一圈。

## 形状描述

每页 `shapes` 为数组，按数组顺序绘制，后面在上面。名称应唯一；`id` 默认从 2 递增，不能重复。每项：

| kind | 内容 |
|---|---|
| `text` | `text` 字符串或 `paragraphs`；透明文本框 |
| `shape` | `geometry`、`fill`、`lineColor`；可有文字 |
| `image` | 本地 `path` 或 `data` 字节串；只支持 PNG/JPEG |
| `table` | `rows` 矩形二维数组；逐格保存值为文本，不重算公式 |
| `chart` | `chart` 指定类型、分类、系列；原生ChartML与独立内嵌XLSX |

通用字段包括 `name,id,x,y,width,height,rotation,description,animation`。文本样式包括 `fontName,fontSize,color,bold,italic,align,valign,margin`。对齐为 `left/center/right/justify`，垂直对齐为 `top/center/bottom`。矩形几何支持 `rect,roundRect,ellipse,triangle,diamond,line,chevron,hexagon`；线宽为 0～1584 点。

文本用 CR/LF 分段，垂直制表符 `\v` 表示软换行；TAB 保留。明确的 `paragraphs` 每项为字符串或 `{text,runs,bullet,level,align}`。运行 `runs` 每项为字符串或 `{text,bold,italic,color,fontName,fontSize}`；运行内部不要放 CR/LF。替换不会把 `&amp;` 当作预转义实体，所有文字按原样内容转义。非法 XML 控制字符、无效 UTF-8 和孤立代理项拒绝。

```aardio
import fsys.openXml.pptx.builder;
var slides = [{shapes=[{
    kind="text";name="Body";x=50;y=50;width=800;height=260;
    paragraphs=[
        {runs=[{text="中文 ";bold=true},{text="彩色文字";color="E55A33"}]},
        {text='第一项\v软换行';bullet=true;level=0},
        {text="第二项";bullet=true;level=1}
    ];
}]}];
return fsys.openXml.pptx.builder.write("/rich.pptx",slides);
```

图片原字节内嵌，不下载 URL、不转码。只给宽或高时保持比例；都省略时按 96 DPI 换算为 point。两者都给时，`fit="stretch"` 默认保持旧的拉伸行为；0.7.0 起可用 `contain` 完整等比放入目标框，或 `cover` 等比铺满并裁切。`imageAlignX/imageAlignY` 在 0..1 指定锚点，默认 0.5 居中（0=左/上，1=右/下）。contain 改变实际图片形状的尺寸与位置，空余区域没有额外图片背景；cover 保持指定框，用原图裁切比例而不重编码。0.8.0 起内容相同的图片（无论来自 `path` 还是 `data`）只嵌入一份，所有页面共用同一媒体部件。

表格每行宽度须一致。中间的 `null` 显示为空文本，`0/false/空串` 分别保留；**尾部空格请用 `""` 明确表示**，因为尾部 null 不保留数组长度。0.11.0 起可用 merges 指定矩形合并，不做自适应列宽。默认等分行列，0.7.0 起支持下述细化样式。

## 0.7.0：填充与精细排版

### 填充

`fill/background/cellFill/bandFill` 支持：

- `"RRGGBB"` 或 `false`：纯色或无填充，兼容旧调用。
- `{color="FFFFFF";opacity=0.35}`：纯色带透明度；opacity 是**不透明度**，范围 0..1，0 完全透明，1 不透明。
- `{angle=45;stops=[{position=0;color="123456"},{position=1;color="468ACE";opacity=0.8}]}`：线性渐变。2~32 个 stop，position 在 0..1 且升序排列；各点 opacity 默认 1。角度使用度数，0 从左向右，90 从上向下，随形状旋转。暂不支持径向渐变、纹理；0.10.0 起支持简单外阴影 shadow。

### 文本

- `margin` 可为单个 point 数值或 `{left,right,top,bottom}`。形状未指定的边默认 4 point，表格默认 3 point；0 被保留。
- `lineSpacing` 为百分比（100=单倍，125=1.25 倍），范围 1~1000。
- `spaceBefore/spaceAfter` 为段前/后距离（point，0~1584）。这三个字段可写在形状上，也可在各段落上覆盖；不指定时保持旧行为。
- 生成时不自动缩小字号；可先调用 0.8.0 的 `builder.check` 预检文字溢出（见下文）。

### 表格

- `columnWidths/rowHeights` 为 point 数组，数量须分别等于列/行数，合计须等于表格 width/height（容差 0.01 point）。省略则等分；舍入余量归末项。
- `header={fill,color,bold,...}` 为第一行样式；`bandFill` 为隔行底色。存在 header 时，从第二条正文行（表格第 3 行）开始间隔填色，否则第 2 行开始。
- 单元格除普通值，还支持 `{text,paragraphs,fill,color,bold,fontSize,margin,valign,...}`。优先级：表格默认 → 隔行 → 表头 → 单元格。false 与 0 可显式覆盖；空 cell 表表示空文本。
- `borderColor` 为 RRGGBB，`borderWidth` 为 point，默认 0.5；可以在表格、header 或单元格中指定四边同样式边框。
- 表格单元格的边距/垂直对齐写在 tcPr，不再叠加 bodyPr 边距；不会调用 Office 来计算表格布局。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{
    background="F5F7FB";
    shapes=[{
        kind="shape";name="Title";x=50;y=40;width=860;height=110;
        fill={angle=20;stops=[{position=0;color="15314F"},{position=1;color="36798B"}]};
        text="可编辑的排版与样式";fontSize=32;color="FFFFFF";
        margin={left=24;right=24;top=18;bottom=18};valign="center";
    },{
        kind="table";name="Grid";x=50;y=190;width=860;height=180;
        columnWidths=[220,320,320];rowHeights=[50,65,65];
        fontSize=20;valign="center";margin=12;
        cellFill="FFFFFF";bandFill="E9EFF7";
        header={fill="203C61";color="FFFFFF";bold=true};
        rows=[["项目","说明","数值"],["保留类型","零不是缺失值",0],
            ["局部强调",{text="可单独设置颜色";color="168A76";bold=true},false]];
    }];
}];
return fsys.openXml.pptx.builder.write("/styled.pptx",slides);
```

完整 6 页示例：`~/examples/File/OpenXML/pptx-design.aardio`，资源在同目录 `.pptx-design/`。包含封面、叙事卡片、形状柱状图、图片适配、原生表格和点击动画。示例图表数据为虚构展示数据；柱条是普通形状，不是含工作簿的原生 chart。

## 0.8.0：生成前预检

`builder.check(slides,options,checkOptions)` 接收与 `write` 完全相同的参数，不启动 Office、不写文件、不修改输入。先做与生成相同的校验（不合法时返回 `null,err`），再估算版式：

| code | 含义 |
|---|---|
| `overflow` | 文字估算高度超过文本区（已扣除内边距）；内边距大于形状为 `error`，其余为 `warning` |
| `tableGrow` | 表格某些行放不下内容，PowerPoint 打开时会自动增高；给出每行需要的高度与总增高 |
| `offSlide` | 形状（含旋转后的外接框、表格自动增高后的高度）超出页面 |
| `overlap` | 两个**含文字**的形状或表格的区域相交；文字叠在无字卡片、图片上不报 |

```aardio
import fsys.openXml.pptx.builder;
var slides=[{shapes=[
    {kind="text";name="Title";x=40;y=40;width=300;height=40;fontSize=20;
        text="这个标题太长了，三百点宽的文本框放不下这么多字"}
]}];
var report,err=fsys.openXml.pptx.builder.check(slides);
if(!report) return null,err;
for i,item in report.issues {
    print(item.code,item.message); // overflow 第 1 页形状 Title：文字可能溢出：约需 48pt，可用 32pt（2 行）
}
return report.ok;
```

返回 `{ok,issues,counts,errors,warnings,missingFonts}`。每个问题含 `code,severity,message,slide,shape` 以及细节字段（`needed/available/lines`、`rows/grow`、`bounds`、`other/area`）。`checkOptions` 可设 `tolerance=0.5`（point）、`overlapMinArea=4`、`ignore=["overlap"]`；单个形状可设 `checkIgnore=true` 或 `checkIgnore=["overflow"]`，表示该处的重叠或溢出是有意设计。`missingFonts` 列出系统缺少、测量时已回退到微软雅黑的字体。

**估算原理与精度**：用 GDI+ 通用排版格式测行数，再按 PowerPoint 行高规律计算（单倍行高 = 1.2×字号；百分比行距时文本框首行只计入约 48% 的额外行距；表格行高 = 内容高度 + 单元格上下边距，空单元格仍占一行）。与本机 PowerPoint 16 实测对照：设计演示 54 个文本框高度平均误差约 0.1%、最大约 1.6%；24 个自动折行样本中 21 个行数完全一致，其余 3 个都是中文窄栏多估 1 行（PowerPoint 允许行尾标点悬挂到边界外，GDI+ 不支持），**从未少估**；表格自动增高后的 10 行行高全部一致。这些样本中没有少估，但不保证对所有字体与输入都不漏报。它仍是估算，不是排版引擎：不模拟逐运行混排、字距、首字下沉，仅针对 PowerPoint 实测过；WPS 未逐项校准。

## 0.8.0：字体、语言、备注与线条

- **分文种字体**：`fontName` 同时用于拉丁、东亚、复杂文种；也可用 `fontNameLatin/fontNameEa/fontNameCs` 分别指定，例如英文用 Segoe UI、中文用微软雅黑。可写在运行、段落、形状或 options 上，逐级继承；某一级显式写了 `fontName` 会覆盖外层的分文种设置。
- **主题字体**：内嵌主题的正文/标题字体随 options 的字体改写（不再是“等线”），用户之后在 PowerPoint 里新加的文字与生成文字默认一致。标题主题字体可用 `headingFontName`（或 `headingFontNameLatin/headingFontNameEa`）单独指定。
- **语言**：`lang` 默认 `zh-CN`，可在形状或 options 上改为 `en-US`、`ja-JP` 等 BCP 47 标签，影响拼写检查与断词。
- **备注字号**：备注默认 12pt（PowerPoint 备注页常用字号），不再继承正文 `options.fontSize`；可用 `options.notesFontSize` 指定。
- **线条**：`lineDash` 为 `solid/dot/dash/lgDash/dashDot/lgDashDot/lgDashDotDot/sysDash/sysDot/sysDashDot/sysDashDotDot`；`lineStart/lineEnd` 为箭头类型 `triangle/stealth/diamond/oval/arrow`（`true` 表示三角），或 `{type,size}`，size 为 `sm/med/lg`。`geometry="line"` 时可直接给起点 `x,y` 与终点 `x2,y2`，边界框与翻转自动计算，斜线不必再换算旋转角；线条默认无填充。
- **错误定位**：生成失败的消息带页号与形状名，例如 `第 2 页 形状 Title：无效的生成参数：fontSize`、`第 1 页 切换：未支持的幻灯片切换：unknown`，不再夹带库文件路径与行号。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{notes="备注默认 12pt";shapes=[
    {kind="text";name="Mixed";x=60;y=60;width=600;height=60;fontSize=28;
        text="Native PPTX 原生生成";fontNameLatin="Segoe UI";fontNameEa="Microsoft YaHei"},
    {kind="shape";name="Arrow";geometry="line";x=80;y=300;x2=420;y2=180;
        lineColor="E55A33";lineWidth=2;lineDash="dash";lineEnd="triangle"}
]}];
return fsys.openXml.pptx.builder.write("/fonts-lines.pptx",slides,{headingFontName="SimHei"});
```

## 动画

`shape.animation` 可为一个动画表或数组，作用于自身。`slide.animations` 为按页面指定顺序的动画数组，`target` 用形状 id 或唯一名称；它追加在形状自身动画之后。

支持：

| effect | 默认类别 | 说明 |
|---|---|---|
| `appear` | entrance | 立即出现；exit 为立即消失 |
| `fade` | entrance | 淡入/淡出 |
| `fly` | entrance | 从指定侧飞入；exit 飞向指定侧 |
| `wipe` | entrance | 擦除；exit 为退场擦除 |
| `zoom` | entrance | 缩放并淡入；exit 缩小并淡出 |
| `spin` | emphasis | `angle` 默认 360 度，可负值 |
| `grow` | emphasis | `scale` 默认 150，表示百分比 |

类别字段 `category` 为 `entrance/exit/emphasis`；`spin/grow` 只属于 emphasis，其他属于 entrance/exit。`direction` 为 `top/right/bottom/left`，飞入与擦除默认 bottom。**这些是本库已验证的名称，不是 COM 的 MsoAnimEffect 数值；COM 常量与 XML presetID 不可混用。**

`trigger` 为 `click/with/after`：点击开始、与同组前动画同步、等前一并行组结束；首个 with/after 可自动开始。`duration/delay` 为秒，默认 0.5/0；appear 固定 1 毫秒。after 的起点等待前一并行组最长动画（包括延迟）结束。每次新 click 建立新点击组。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{shapes=[{
    kind="shape";name="Badge";x=330;y=160;width=220;height=140;
    geometry="roundRect";fill="4472C4";text="动画";color="FFFFFF";
    align="center";valign="center";
    animation=[
        {effect="fly";direction="left";trigger="with";duration=0.7},
        {effect="spin";angle=90;trigger="after";duration=0.5},
        {effect="fade";category="exit";trigger="click";duration=0.4}
    ];
}]}];
return fsys.openXml.pptx.builder.write("/animated.pptx",slides);
```

支持整形状及逐段落动画、Morph 与明确的 fade 兼容回退；不支持交互触发器、声音、自定义运动路径或所有 PowerPoint 特效。未知效果明确报错，不静默降级。尚未覆盖的复杂时间轴可用 COM 辅助。

## 幻灯片切换与备注

`transition` 可含 `effect,duration,direction,orientation,advanceOnClick,advanceAfter,option`。效果为 `none/cut/fade/push/wipe/split/morph`；Morph 及其 fade 回退见上面的 0.9.0 一节；push/wipe 用四方向，split 用 `direction=in/out,orientation=horz/vert`。`duration/advanceAfter` 为秒，advanceOnClick 默认 true。写出 Office 2010 扩展 Choice 与标准 Fallback；老软件只得到 fast/med/slow 分类，不保证精确时长。

`notes` 字符串生成逐页备注，空字符串仍生成备注部件；不传则没有。备注母版有独立主题，避免 SDK 良构但 PowerPoint 不能打开的兼容性问题。`hidden=true` 保存隐藏页。

## 限额与边界

- 最多 1000 页、每页 10000 形状/展开后最多10000个动画、单个表格 100000 格。
- `maxEntries=10000,maxPartBytes=16777216,maxWriteBytes=134217728`；最高 100000 项、128 MiB/件、1 GiB 累计，均为正整数。
- 生成器在内存中构造所有部件；图片解码内存不等同压缩文件大小，不能当作无限大媒体处理器。
- `buildParts(slides,options)` 返回部件数组，适合测试/受控扩展，不是完整布局/样式对象模型。
- ZIP 写入与关闭均检查返回值，同目录临时 ZIP 成功后提交；失败不留下半成品最终文件。
- 不生成旧二进制 `.ppt`、宏、上述四种二维图以外的图表、SmartArt 或复杂主题/母版；不自动分页、不自动缩字、不嵌入字体、不渲染 PDF/图片。文字测量只用于 `check` 预警，不改变生成结果。
- SDK 结构校验、PowerPoint 打开/动画属性核对、静态图片检查是不同验证层次。动画实时视觉播放不能由静态图片证明；WPS 行为未逐项实测。
