跳转至

[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;越界 slide fail-fast;同页混用 click 与 after 直接拒绝。
  • MCP Experimental 工具默认隐藏(CF-6,行为变化):schema 为每个操作声明 formal / advanced / experimental tier;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 改为 members ensemble + 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-in feedback_severity_adjustments,报告 schema 升 0.3(含严重度来源溯源)。
  • CLI 退出码契约(行为对齐):通用命令 InvalidArgumentError → exit 2(使用 / 参数 / 预运行 无效输入)、OffipyError → exit 1(运行时领域失败);audit 0/1/2/3、deck audit 0/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.9 read_slide_texts() 相同,存量代码 只改方法名即可。
  • 新增 op 只需改 src/offipy/ppt.py + src/offipy/schema.py,server/CLI/MCP/ stub/文档自动派生。