[Unreleased]
- 场景融合补缺(#142,行为变化):
merge_scenes/_merge_element现以 measurement 为主、pptx 补缺——primary 缺字段(None / 空文本)时用 secondary 补齐 (字号三元组同源,避免 px/pt 混用)。若测量源缺前景色 / 背景色 / 字号 / 透明度 / fill_kind,合并结果现在会带上 pptx 审计提供的证据(source="merged"语义不变)。 audit 侧图表框架透明度、drawio 折线边识别/透明为纯新增行为,无迁移动作。 - request_id 契约收口(CF-5,行为变化):幂等去重只保证在同一 Offipy Server 进程生命周期
内;server 重启/崩溃后结果可能未知,破坏性操作需先 read-back。
RemoteCallError.request_id暴露自动生成或显式传入的请求 ID,超时响应也会回显该 ID。 - 动画声明校验(#162/#163/#164,行为变化):
deck add-anim对未知字段返回友好InvalidArgumentError/exit 2;越界slidefail-fast;同页混用click与after直接拒绝。 - MCP Experimental 工具默认隐藏(CF-6,行为变化):schema 为每个操作声明
formal/advanced/experimentaltier;feedback_*等 Experimental 工具默认不注册到 MCP。开发者若需要试用,须在启动 MCP 进程前设置OFFIPY_MCP_INCLUDE_EXPERIMENTAL=1;CLI / HTTP 路径保持可用,商业 1.0 不把这些操作列入默认 Agent 工具契约。
v0.17.1 → v0.18.0
0.18 是纯新增 + 行为对齐版本:不改任何既有 API / CLI 契约。唯一注意点是 feedback 学习模型 / 记录的 schema 版本已升级——旧模型与旧记录需重新标注 / 训练。
- FEATURES feature schema v1 → v2(#115):媒体特征新增
art.media.tiny_image.area_ratio。旧 JSONL 记录(feature_schema_version="1") 不再是可训练样本(valid_samples会把他们筛掉),需用当前feedback append重新标注后再训练。 - model schema v1 → v2(#118/#120):model.json 改为
membersensemble +preprocessing/calibration/abstain块。0.17.x 训练的旧模型(schema v1) 不再加载——feedback status报model: none(v1 文件缺少 v2 必需字段 (members / preprocessing / calibration / abstain 等),过不了load_model完整性校验 → 视为无模型),推理侧自动回退 v2 行为(recommend_adjustments)。 想恢复学习:用新标注记录feedback train重建(旧 model.json 会被原子覆盖)。 feedback status输出新增键(#121/#119,非破坏):模型有效时额外返回effective_dims/samples_per_param/poor_generalization,旧字段不变。
v0.13.2 → v0.14.0
v0.14 引入资源系统(Asset System v1):统一 asset:// 资源管线(provider →
测量 → 占位符注入 → 渲染落位)。对既有 deck HTML 无破坏,旧 data-icon 图标写法
无需改动,内部迁移到 ph / lu provider。
- 新规范声明(新增,可选):
data-asset="asset://<provider>/<kind>/<name>"+data-asset-param-*(按 provider schema 传参)+data-asset-placement(replace默认 /decorative/background)。原生图元可用data-primitive简写(预处理展开成规范data-asset)。完整语法见 资源系统。 no_visual_audit前置检查扩大(行为变化):--no-visual-audit现在除了 拒绝data-chart/data-icon,也拒绝data-asset/data-primitive声明 (fail-fast,chromium 启动前)——资源注入依赖 visual audit 的measurements.json。- visual-audit 渲染产出
assets.json(新增):输出目录(如out_audit/)内新增assets.json用法清单,记录每个资源的 provider / 许可证 / 来源 / 落位。 no_visual_audit不产资产清单(行为约束):no_visual_audit渲染没有审计目录, 也不产出assets.json。- 原生图元不带嵌套资源(v0.14 限制):
device-frame/browser-mockup没有screenshot/src/image参数,屏幕是空的可编辑区域;background落位对 原生图元显式报错。 - 内置 provider:
ph(1512 Phosphor 图标)/lu(1756 Lucide 图标)/procedural(8 个确定性纹理)/primitives(8 个原生图元)。
v0.12.2 → v0.13.0
v0.13 是纯新增 + CLI 行为对齐版本:不破坏任何 0.12 的既有 API / 返回契约。现有代码无需改动, 无迁移步骤。
- PPT 形状读取与编辑(新增):
read_shapes()(冻结ShapeInfo/ 形状类型契约)+set_shape_*/delete_shape/set_shape_z_order增量编辑;Python API / RPC / MCP / CLI 全暴露。 - Word
add_page_number三模式(新增):append(默认,幂等)/ standalone(left/center/right); standalone 保留页码域、清空用户 tab stop(页脚内容会被清)。 - 艺术分析反馈学习 v2(新增,仅建议):
build_scene/analyze_deck可 opt-infeedback_severity_adjustments,报告 schema 升 0.3(含严重度来源溯源)。 - CLI 退出码契约(行为对齐):通用命令
InvalidArgumentError → exit 2(使用 / 参数 / 预运行 无效输入)、OffipyError → exit 1(运行时领域失败);audit0/1/2/3、deck audit0/1 专属契约保留。之前部分预运行时错误可能落在不同退出码,现已统一冻结——依赖退出码的 CI 请按 docs/usage.md「CLI 退出码契约」核对。
v0.12.1 → v0.12.2
0.12.2 是纯修复版本(#33-#37):不破坏任何既有 API、CLI 行为或返回契约。现有代码无需改动, 无迁移步骤。
- 版本偏斜自愈(#34):client 探测到 8890 上驻留旧版 offipy server(协议匹配但版本不一致) 时判定为 stale 并自动重启,不再静默连旧版本。
- Excel 畸形区域 fail-fast(#35):
set_range/read_range/set_border非法地址统一抛InvalidArgumentError(不再穿透原始 COM 错误)。 - 保存锁重试(#37):
save/save_pdf目标文件被其他进程占用时,库内短重试后给可读错误。 - HRESULT 显示 + 线程契约提示(#36):负 HRESULT 两补码显示;
CO_E_NOTINITIALIZED报错提示com_apartment()。
0.11 → 0.12 迁移指南
0.12 是纯新增版本:不破坏任何 0.11 的既有 API、CLI 行为或返回契约。现有代码无需改动。
新增(全部可选项)
- offipy.art 艺术分析:
build_scene(measurements=..., pptx=...)建场景 +analyze_scene(scene, profile=...)评估(5 维度规则),grade / confidence / evidence_coverage 三分离、证据不足降级insufficient_evidence;只建议不阻断,无总分门禁。纯标准库,import offipy即有(不加载 python-pptx / AI / COM)。 - 组合入口:
analyze_deck(pptx=..., measurements=..., profile=...)一次调用几何审计 + 艺术分析, 产出DeckQualityReport(.geometry/.art/.warnings)。 - 生成即质量参考:
deck.render_with_quality_report(html, audit_mode=..., fail_on=..., profile=...)——并行新增入口,render_with_report契约完全不变。render_with_quality_report在几何审计之外 再产出艺术分析,返回QualityRenderResult(含art_report/deck_quality)。 - 内置 profile:
balanced/consulting/academic/technology/event。 - 基线对比 v2:
compare_reports(before, after)产出ArtReportDiff。 - CLI / RPC / MCP 三入口行为不受影响;完整 art API 见 docs/art.md。
迁移步骤
0.11 → 0.12 无迁移步骤。如果 0.11 代码能跑,0.12 直接换版本号即可。
唯一注意点(非破坏):
- 想给 deck 生成附带艺术分析,把
render_with_report换成新的render_with_quality_report(后者在几何审计基础上追加艺术分析);不想用就直接忽略,不影响。
0.10 → 0.11 迁移指南
0.11 是纯新增版本:不破坏任何 0.10 的既有 API、CLI 行为或返回契约。现有代码无需改动。
新增(全部可选项)
- PPTX 静态质量审计:
audit_pptx(path, config=None)纯解析.pptx(ZIP+XML), 不开 PowerPoint、不依赖 Microsoft Office,检查越界 / 贴边 / 重叠 / 文本溢出 / autofit 风险, 产出PptxAuditReport(text / json / markdown / html)。 - 基线回归:
compare_pptx(baseline, candidate)产出PptxDiffReport, 聚合新增 / 已解决 / 变化的问题与形状增删移动缩放文本变化;--fail-on-new只阻断候选 新增或恶化的问题。 - Deck 生成门禁:
deck.render_with_report(html, output, audit_mode="report"|"strict", fail_on=...)。render()签名与行为完全不变,只是新增了这个带审计的变体。 - CLI:
offipy audit子命令(参数与退出码见 docs/audit.md)。
迁移步骤
0.10 → 0.11 无迁移步骤。如果 0.10 代码能跑,0.11 直接换版本号即可。
唯一注意点(非破坏):
import offipy/import offipy.audit仍然不加载 python-pptx(惰性 import 硬约束); 只有真正audit_pptx/compare_pptx解析文件时才需要,且解析依赖 python-pptx (pip install offipy[deck])。offipy audit的退出码语义与其它命令不同:0=未达门槛 / 1=达--fail-on或--fail-on-new/ 2=参数或输入错误 / 3=依赖或解析错误。这是 audit 子命令内部自捕异常的结果, 不影响其它子命令的OffipyError → 1行为。
0.9 → 0.10 迁移指南
0.10 对 PowerPoint 读 API 做了一次破坏性重构:旧的 read_slide_texts()(无参,
返回全部页的 {index, title, body, notes} 摘要)拆成了两个职责清晰的 API。
同时在占位符常量上修正了一个既有 Bug(P0)。
本文只讲怎么迁移;完整 API 见 docs/api。
破坏性变更:read_slide_texts 签名
发生了什么
| 0.9 | 0.10 |
|---|---|
ppt.read_slide_texts() → 全部页摘要 list[dict] |
ppt.read_slide_summary() → 全部页摘要 list[dict](语义不变) |
| — | ppt.read_slide_texts(slide_idx, *, include_empty=False, recursive=True) → 单页 per-shape 文本记录 list[SlideTextRecord] |
read_slide_texts 现在是按页、按 shape 读文本;slide_idx 是必填位置参数。
read_slide_summary 承接旧的全部页摘要行为。
迁移步骤
旧用法(0.9)——要全部页摘要:
items = ppt.read_slide_texts() # 全部页 {index,title,body,notes}
改为(0.10)——同一语义:
items = ppt.read_slide_summary() # 依旧 {index,title,body,notes}
改为(0.10)——要某一页的逐 shape 文本:
records = ppt.read_slide_texts(slide_idx=1)
for r in records:
print(r["shape_id"], r["name"], r["text"])
传错参数会发生什么
旧调用 ppt.read_slide_texts() 现在会抛 Python 标准错误:
TypeError: read_slide_texts() missing 1 required positional argument: 'slide_idx'
这是刻意设计(方案 A):slide_idx 保持必填,不引入运行时拦截或告警,
让缺失调用立即显式失败,避免长期签名被污染。迁移者按上面的「改为」改写即可。
新增:read_slide_summary
返回逐页摘要,字段与 0.9 的 read_slide_texts() 输出一致:
{"index", "title", "body", "notes"}。
- title:标题/居中标题占位符(type 1/3)优先;否则按稳定阅读顺序回退第一个非空非豁免文本。
- body:正文占位符(type 2)优先;否则其余文本 shape 按阅读顺序
"\n"拼接。 - 空文本 shape 一律不进 title/body(0.10.1 行为修正:title 回退曾漏过滤空文本——header 背景矩形等带空 TextFrame 的 shape 会按阅读顺序排在真标题前抢占 title,导致所有页 title 空串,真实比赛 PPT 实证。0.10.1 起 title/body 都跳过空文本候选,对齐
read_slide_texts的include_empty=False语义)。 - 豁免集:页码/页眉/页脚/日期占位符(type 13/14/15/16)+「页码候选」 (纯数字 AND 位于页面底部/角落 AND 尺寸较小)不进 title/body。
- 对标准标题/正文占位符页面与 0.9 行为一致;纯文本框页面为启发式摘要, 排序稳定、语义一致,不承诺与 0.9 逐字节一致。
新增:read_slide_texts(v2 语义)
read_slide_texts(slide_idx, *, include_empty=False, recursive=True) 返回
第 slide_idx 页全部具有文本能力的 shape 的记录,元素类型是
SlideTextRecord(TypedDict)。
- 只返回有 TextFrame 的 shape;图片/线条/无文本图形不在此列。
include_empty=True连文本为空的 TextFrame shape 也返回。recursive=True递归 group 内文本;非旋转 group 子元素坐标是幻灯片绝对坐标 (coordinate_space="slide"),旋转 group 内不可信(coordinate_space="unknown")。- 坐标单位恒为磅(pt)。
SlideTextRecord 数据模型
class SlideTextRecord(TypedDict):
shape_id: int
name: str
text: str
left: float
top: float
width: float
height: float
coordinate_space: Literal["slide", "unknown"]
coordinate_unit: Literal["pt"]
is_placeholder: bool
placeholder_type: int | None # PpPlaceholderType 数值
placeholder_type_name: str | None # 完整映射 + "unknown_{n}" 兜底
parent_shape_id: int | None
group_path: list[int] # group 祖先 shape_id 链(外层→内层)
真机行为(探针实证):PowerPoint COM 会拍平嵌套 group——打开含「外层 grpSp 包内层 grpSp」的文件时,内层 group 不出现在对象模型中,其子元素直接成为 外层
GroupItems成员,且Left/Top/Width/Height已换算为幻灯片绝对坐标。 因此group_path反映 COM 对象模型的实际结构,通常为单层(如[300]);parent_shape_id指向直接父 group。「多层祖先链」仅在 COM 真提供嵌套 group 时出现(_iter_shapes会正确递归),真实 PowerPoint 生成的嵌套 group 一律拍平。coordinate_space="slide"对所有 group 子元素成立(坐标本就是幻灯片绝对坐标)。
类型可从 from offipy import SlideTextRecord / PLACEHOLDER_TYPE_NAMES 导入,
mypy 能推导字段类型(见 tests/test_api_stub.py::test_mypy_user_example_reveals_sliderecord_types)。
附带修正:占位符常量(行为变化,非 API 破坏)
0.10 修正了 src/offipy/ppt.py 的占位符常量(P0 Bug):
| 常量 | 0.9(错误) | 0.10(微软官方值) |
|---|---|---|
PP_PLACEHOLDER_TITLE |
13(实为 slideNumber) | 1 |
PP_PLACEHOLDER_CENTER_TITLE |
14(实为 header) | 3 |
PP_PLACEHOLDER_SLIDE_NUMBER |
— | 13 |
PP_PLACEHOLDER_HEADER |
— | 14 |
PP_PLACEHOLDER_FOOTER |
— | 15 |
PP_PLACEHOLDER_DATE |
— | 16 |
影响:set_title / set_body / set_notes(_placeholder_by_type)在标准布局上
现在能找到真正的标题/正文占位符,行为更正确——例如对「标题+内容」布局调用
set_title 之前会建文本框、现在会写入标题占位符。如果你的代码依赖 0.9 的错误
常量值做占位符类型判断,请改用 from offipy import PLACEHOLDER_TYPE_NAMES
(完整映射,含 unknown_{n} 兜底)。
不影响
- Word/Excel 的
read_*操作不变。 read_slide_summary的返回值字段与 0.9read_slide_texts()相同,存量代码 只改方法名即可。- 新增 op 只需改
src/offipy/ppt.py+src/offipy/schema.py,server/CLI/MCP/ stub/文档自动派生。