# 页码、超链接、编号与外观细节

以下能力需要 `fsys.openXml` **0.10.0+**。生成器直接写入原生 DrawingML/PresentationML，不启动 Office、不访问链接。完整五页演示：`~/examples/File/OpenXML/pptx-details.aardio`。

## 页码字段

- `options.slideNumber=true` 在每页添加一个名为 `SlideNumber` 的文本框，默认右下角、12pt、灰色；默认不添加。
- 可传样式表：`slideNumber={x,y,width,height,fontSize,color,align,margin,prefix,suffix,...}`。单位 point；常用文字与形状样式适用。不要再指定 text/paragraphs/placeholder。
- `slide.slideNumber=false` 可隐藏该页页码；`true` 或样式表可单页启用/替代全局配置。隐藏页码不改变页序计数，封面仍占一页。
- `options.firstSlideNumber` 为起始页码，默认1，支持0，范围0~2147482647；同时写入 presentation.firstSlideNum，并生成正确的初始字段缓存。
- 任意文本框或表格单元格内可使用运行 `{field="slideNumber"}`。它是 `<a:fld type="slidenum">`，不是普通数字字符串；不能同时指定 text。
- 前后缀是普通文字，例如 `suffix=" / 5"`；**总页数不是动态字段**。修改稿件页数后请自行更新总页数文字。
- 页码字段由播放/编辑应用更新。本机 PowerPoint 已验证重排页面后自动更新。只读抽取读取保存的缓存，不计算字段；原生 editor.reorderSlides 也不会重算缓存，需由 Office/WPS 更新。兼容应用如何刷新由该应用决定。
- 自动页码与显式字段共用解析模型，预检使用对应页面实际页码，不会把字段当空文本。自动页码占用一个普通形状，命名/ID冲突会明确报错，可在样式表中改 name/id。

```aardio
import fsys.openXml.pptx.builder;
var slides=[
    {slideNumber=false;shapes=[{placeholder="title";text="封面"}]},
    {shapes=[{x=60;y=80;width=700;height=90;paragraphs=[{
        runs=[{text="当前页："},{field="slideNumber"}]
    }]}]}
];
return fsys.openXml.pptx.builder.write("/numbered-pages.pptx",slides,{
    firstSlideNumber=0;
    slideNumber={suffix=" / 2";color="59717E"}
});
```

## 点击超链接

`shape.hyperlink` 用于整个 text/shape/image；`runs` 中的 `hyperlink` 仅作用于该段文字，也支持表格单元格运行。暂不支持整表点击、鼠标悬停动作、文件/程序/宏动作。

| 输入 | 行为 |
|---|---|
| `"https://www.aardio.com"` | 外部网页，等价 `{url="..."}` |
| `{url="mailto:hello@example.org"}` | 邮件链接；仅由用户点击时交给应用处理 |
| `{slide=3}` | 跳到本次生成的第3页；使用内部 slide 关系，不是依赖标题文字的地址 |
| `{action="first"}` | 跳到首页 |
| `{action="previous"/"next"/"last"}` | 上页、下页、末页 |
| `{action="back"}` | 返回上一次观看的页面（由播放应用维护历史） |
| `{action="end"}` | 结束放映 |

url/slide/action 三者只能指定一项；表中可加 `tooltip` 提示文字。slide 从1开始（与 firstSlideNumber 无关），必须在文稿范围内。内部关系在 editor 重排时仍指向原页面，而不是重排后的同一页码。

外链仅允许 http/https/mailto；不下载、不跟随、不校验网络可达性。空白/控制字符应由调用者按 URL 百分号编码，生成器只做 XML 转义，不把已有的 `%20` 等再编码。拒绝相对文件路径、UNC、file、javascript 与其他动作协议。对生成的 PPT 手动点击外链仍可能打开浏览器/邮件程序，受应用自身安全设置控制。

**不自动改变文字颜色**。需要常见链接样式时显式指定 `color` 与 `underline=true`（文本/段落/运行支持布尔 underline，可用 false 清除）。形状点击与局部文字点击分别写入，后者不会延伸到段落结束/软换行标记。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{shapes=[
    {kind="shape";name="Next";x=60;y=80;width=220;height=70;
        text="下一页";color="FFFFFF";align="center";valign="center";
        hyperlink={slide=2;tooltip="进入内容页"}},
    {x=60;y=190;width=600;height=70;paragraphs=[{runs=[
        {text="查看 "},{text="aardio 官网";color="2468B4";
            underline=true;hyperlink="https://www.aardio.com"}
    ]}]}
]},{shapes=[{x=60;y=80;width=300;height=70;text="回到首页";
    hyperlink={action="first"}}]}];
return fsys.openXml.pptx.builder.write("/linked-pages.pptx",slides);
```

## 原生编号列表

在段落上指定 `numbering=true`（arabicPeriod）、类型字符串或 `{type,startAt}`；不能与 `bullet` 同时启用。

支持12种：`arabicPeriod/arabicPlain/arabicParenR/arabicParenBoth`、`alphaLcPeriod/alphaUcPeriod/alphaLcParenR/alphaUcParenR`、`romanLcPeriod/romanUcPeriod/romanLcParenR/romanUcParenR`。

- `startAt=1..32767` 表示该序列的起始基数；后续同层同类型省略 startAt 时沿用基数，实际数值由 Office/WPS 自动编号。支持 level=0..8，每个层级保存其基数；非编号段落结束当前连续列表。
- 从3开始的三个连续段落只需首段写 `{startAt=3}`、后两段写 true，显示3、4、5。**不要把后两段的 startAt 分别写4、5**；那表示改变序列的基数。
- 与原生编号规则一致，相邻同类型段落反复指定相同 startAt 不等于“每段重新开始”。要强制重新开始同值列表，请另起文本框，或使用非编号段隔开。
- `marginLeft` 为正文左缩进，`hanging` 为首行悬挂宽度，单位 point；0也保留，且0≤hanging≤marginLeft≤1584。编号默认缩进随段落字号变化，level 每级增加18pt。原有 bullet 的默认22pt缩进/15pt悬挂保持不变。
- 预检按同一 marginLeft 扣除正文宽度，检查折行与高度；不是编号字形排版器。长罗马编号/多位数字或混合大字号请显式增大缩进与悬挂宽度，并检查渲染。
- 数字不拼入 a:t，因此可编辑的编号不会混入只读抽取的正文。软换行仍属于同一项；空编号段落按应用的原生行为处理。

```aardio
import fsys.openXml.pptx.builder;
var slides=[{shapes=[{x=60;y=80;width=780;height=300;fontSize=26;
    spaceAfter=16;paragraphs=[
        {text="定义目标";numbering={startAt=3};marginLeft=60;hanging=40},
        {text='组织内容\v这一行仍属于同一项';numbering=true;marginLeft=60;hanging=40},
        {text="检查结果";numbering=true;marginLeft=60;hanging=40}
    ]
}]}];
return fsys.openXml.pptx.builder.write("/numbered-list.pptx",slides);
```

## 外阴影与圆角半径

- `shadow=true` 使用默认外阴影；false/null 不写阴影。样式表支持 `{color="000000";opacity=0.2;blur=4;distance=3;angle=45}`。
- blur为0~100pt，distance为0~1000pt；angle为角度（0向右，90向下，正数顺时针），相对于页面，不随形状旋转；opacity=0..1是不透明度，不是透明度。0值均保留。
- 外阴影用于 text/shape/image；表格可在后方放置带阴影的背景形状。不做内阴影、透视、缩放阴影等完整效果系统。
- `geometry="roundRect";cornerRadius=12` 指定圆角半径（point），范围0~短边的一半；0为直角，上限为胶囊/最大圆角。不指定时保留原预设半径。不适用于图片裁圆角或其他几何。
- 预检在 offSlide 判断中加入外阴影的偏移和保守模糊边界；完全透明阴影不参与。阴影不作为文字重叠的边界。

```aardio
import fsys.openXml.pptx.builder;
return fsys.openXml.pptx.builder.write("/shape-details.pptx",[{shapes=[{
    kind="shape";geometry="roundRect";cornerRadius=18;
    x=90;y=100;width=420;height=220;fill="FFFFFF";
    lineColor="D8E5E8";text="可编辑的原生卡片";
    color="193546";align="center";valign="center";
    shadow={blur=8;distance=4;angle=90;opacity=0.15}
}]}]);
```

## 验证范围

本机 PowerPoint 验证了真实连续编号、链接关系/内部跳转、移动页面后页码更新、自定义圆角、阴影属性与图片导出；SDK检查结构。新功能尚需按实际使用的WPS版本手动验收，不把旧演示的成功扩大成所有新功能均已验收。

此前 `pptx-show.aardio` 的逐段落/Morph/标准版式演示，用户已于2026年10月8日在两台电脑的WPS中验证播放与排版正常；版本号未提供。旧演示数据未改，本轮对其生成部件做了逐字节回归。
