mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 02:14:02 +08:00
Compare commits
2 Commits
feat/slide
...
feat/slide
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7be4205d0 | ||
|
|
9b05a71de3 |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-slides
|
||||
version: 1.0.0
|
||||
description: "飞书幻灯片:创建和编辑幻灯片。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用。当用户给出 doubao.com 的 /slides/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:本地 `.pptx` / `.pdf` 导入为 slides(走 `lark-drive` 的 `drive +import --type slides`)、云文档内容编辑(走 lark-doc)、云文档里的独立画板对象(走 lark-whiteboard,注意 slide 内嵌的流程图/架构图仍属本 skill)、上传或下载普通文件(走 lark-drive)。"
|
||||
description: "飞书幻灯片:创建和编辑幻灯片。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用;当用户给定 PPTX/PDF/existing Slides 作为模板、底稿或二创对象时,也用本 skill 统筹导入后的二次创作(导入命令本身走 `lark-drive` 的 `drive +import --type slides`)。当用户给出 doubao.com 的 /slides/ URL/token 时,也应直接使用本 skill,不要因为域名不是飞书而回退到 WebFetch;路由依据是 URL 路径模式和 token,而不是域名。不负责:云文档内容编辑(走 lark-doc)、云文档里的独立画板对象(走 lark-whiteboard,注意 slide 内嵌的流程图/架构图仍属本 skill)、上传或下载普通文件(走 lark-drive)。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
@@ -14,8 +14,8 @@ metadata:
|
||||
|
||||
| 用户需求 | 优先动作 | 关键文档 / 命令 |
|
||||
|----------|----------|-----------------|
|
||||
| 新建 PPT | 先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`slides +create` |
|
||||
| 已有 PPT 大幅改写 | 多页整页重建用 `+replace-pages`,单页局部编辑用 `+replace-slide` | `xml_presentations.get`、`lark-slides-replace-pages.md`、`lark-slides-edit-workflows.md` |
|
||||
| 新建 PPT、从空白生成、明确重设计 | 走 Create Workflow:先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`slides +create` |
|
||||
| 用户提供 PPTX/PDF/slides、existing Slides、模板/底稿/原 PPT 二创 | 走 Template Rewrite Workflow:导入/回读成 `source.xml`,从源 XML 生成 replacement slides,经 `pages.json` 执行 `+replace-pages`,再保存 `readback.xml` 验证 | `template-rewrite-workflow.md`、`xml_presentations.get`、`lark-slides-replace-pages.md` |
|
||||
| 编辑单个标题、文本块、图片或局部元素 | 优先块级替换/插入,不改页序 | `slides +replace-slide`、`lark-slides-replace-slide.md` |
|
||||
| 读取或分析已有 PPT | 解析 slides/wiki token,回读全文或单页 XML,保存 `xml_presentation_id`、`slide_id`、`revision_id` | `xml_presentations.get`、`xml_presentation.slide.get` |
|
||||
| 获取幻灯片页面截图 | 用 `slide_id` 或页号指定页面 | `slides +screenshot`、`lark-slides-screenshot.md` |
|
||||
@@ -23,31 +23,37 @@ metadata:
|
||||
| 在 slide 中绘制柱/条/折线/面积/雷达/饼等有数据序列的图表 | 使用原生 `<chart>` 元素 | `xml-schema-quick-ref.md` |
|
||||
| 在 slide 中绘制流程图、时序图、架构图、散点图、漏斗图或装饰图案 | 必须先用 Read 工具读取参考文档,再生成 `<whiteboard>` 元素 | [`lark-slides-whiteboard.md`](references/lark-slides-whiteboard.md) |
|
||||
| 使用语义图标 | 先检索 IconPark,再写 `<icon iconType="...">` | `iconpark_tool.py search → resolve`、`iconpark.md` |
|
||||
| 用户提到模板、主题、版式 | 先检索模板,再摘要,必要时裁切骨架 | `template_tool.py search → summarize → extract` |
|
||||
| 用户提到模板、主题、版式但没有提供本地/在线模板材料 | 先检索内置模板,再摘要,必要时裁切骨架 | `template_tool.py search → summarize → extract` |
|
||||
| 创建失败、空白页、3350001、布局异常 | 先回读状态,再按排障清单修复,不假设原操作原子成功 | `troubleshooting.md`、`validation-checklist.md` |
|
||||
|
||||
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),认证、权限和全局参数均以 lark-shared 为准。**
|
||||
|
||||
**CRITICAL — 生成任何 XML 之前,MUST 先用 Read 工具读取 [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md),禁止凭记忆猜测 XML 结构。**
|
||||
|
||||
**CRITICAL — 新建演示文稿或大幅改写页面时,MUST 先生成 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`,再生成 XML。先创建对应目录,规划层规则和中间产物生命周期见 [planning-layer.md](references/planning-layer.md)。仅替换一个标题、插入一个块等小型已有页编辑可豁免。**
|
||||
**CRITICAL — Create Workflow(新建演示文稿、从空白生成、用户明确要求重设计、没有模板保留诉求)MUST 先生成 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`,再生成 XML。先创建对应目录,规划层规则和中间产物生命周期见 [planning-layer.md](references/planning-layer.md)。**
|
||||
|
||||
**CRITICAL — 新建演示文稿或大幅改写页面时,生成 XML 前 MUST 读取 [visual-planning.md](references/visual-planning.md),确保 `layout_type`、`visual_focus`、`text_density` 实际改变页面几何、主视觉和文本量。**
|
||||
**CRITICAL — Create Workflow 生成 XML 前 MUST 读取 [visual-planning.md](references/visual-planning.md),确保 `layout_type`、`visual_focus`、`text_density` 实际改变页面几何、主视觉和文本量。**
|
||||
|
||||
**CRITICAL — 新建演示文稿或大幅改写页面时,规划 `asset_need` MUST 遵循 [asset-planning.md](references/asset-planning.md):只做元数据规划,必须有 `fallback_if_missing`,不得要求真实搜索、下载或上传素材。**
|
||||
**CRITICAL — Create Workflow 规划 `asset_need` MUST 遵循 [asset-planning.md](references/asset-planning.md):只做元数据规划,必须有 `fallback_if_missing`,不得要求真实搜索、下载或上传素材。**
|
||||
|
||||
**CRITICAL — 创建或大幅改写后,MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素、检查空白/破损页、明显溢出、布局风险;XML 语法和文本重叠静态检查优先使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py)。**
|
||||
**CRITICAL — Template Rewrite Workflow(用户提供 PPTX/PDF/slides、existing Slides、要求基于模板/底稿/原 PPT 二创或保留原版式)MUST 读取 [template-rewrite-workflow.md](references/template-rewrite-workflow.md)。不要读取 `planning-layer.md`、`visual-planning.md`、`asset-planning.md` 来生成二创 plan;不要生成 `slide_plan.json`、`page_rewrite_plan.json`、`rewrite_manifest.json`。固定数据流是 `source.xml -> pages.json -> slides +replace-pages -> readback.xml validation`。**
|
||||
|
||||
**CRITICAL — Template Rewrite 不允许用 `python-pptx` / PowerPoint 自动化清空模板页后从 blank layout 重画,也不允许生成一个只继承模板尺寸/主题色的本地 PPTX 再导入作为最终产物。模板二创必须以 `source.xml` 的每页真实 XML 为骨架;如果 `source.xml` 不可得,停止并说明该工作流被阻塞,不能伪装成模板二创。**
|
||||
|
||||
**CRITICAL — Template Rewrite 必须做 source-connected rewrite:新内容要进入源页已有 text container、图形标签、节点、箭头、时间线、图表/table 或注释容器。不要把模板当背景,再覆盖通用顶栏、三卡片、2x2 卡片、大白卡或重复组件系统;源页 dominant structure 必须继续承载内容。**
|
||||
|
||||
**CRITICAL — 创建、Template Rewrite 或大幅改写后,MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素、检查空白/破损页、明显溢出、布局风险;XML 语法和文本重叠静态检查优先使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py)。**
|
||||
|
||||
**CRITICAL — 创建前自检或失败排障时,MUST 按 [troubleshooting.md](references/troubleshooting.md) 检查 XML 转义、结构、shell 截断、图片 token、3350001 和布局风险。**
|
||||
|
||||
**CRITICAL — 如果用户提到“模板”“套用模板”“参考某种主题/风格/版式”,或用户需求明显落在已有场景模板内(如工作汇报、产品介绍、商业计划书、培训、晋升汇报等),MUST 先用 [`scripts/template_tool.py`](scripts/template_tool.py) 的 `search` 做模板检索;默认给出 2-3 个最匹配模板候选供用户选择。锁定模板后用 `summarize` 获取主题和布局摘要;只有需要布局骨架时才用 `extract` 裁切目标页型 XML。不要直接读取完整模板 XML。**
|
||||
**CRITICAL — 如果用户提到“模板”“套用模板”“参考某种主题/风格/版式”,但没有提供本地/在线模板材料,MUST 先用 [`scripts/template_tool.py`](scripts/template_tool.py) 的 `search` 做内置模板检索;默认给出 2-3 个最匹配模板候选供用户选择。锁定模板后用 `summarize` 获取主题和布局摘要;只有需要布局骨架时才用 `extract` 裁切目标页型 XML。不要直接读取完整模板 XML。**
|
||||
|
||||
> [!NOTE]
|
||||
> `scripts/template_tool.py` 需要 Python 3。`references/template-index.json` 是脚本缓存/轻量路由索引,不是默认给 agent 阅读的文档;`assets/templates/*.xml` 是机器资源,只应通过脚本摘要或裁切,不要全文读取。
|
||||
|
||||
**CRITICAL — 使用模板生成或改写页面时,MUST 先 `summarize` 目标页型;只有需要具体布局骨架时才 `extract`。**
|
||||
**CRITICAL — 使用内置模板生成或改写页面时,MUST 先 `summarize` 目标页型;只有需要具体布局骨架时才 `extract`。用户提供真实 PPTX/PDF/slides/existing Slides 时不要走内置模板工具,走 Template Rewrite Workflow。**
|
||||
|
||||
**编辑已有幻灯片页面**:单个标题、文本块、图片或局部元素优先用 [`+replace-slide`](references/lark-slides-replace-slide.md)(块级替换/插入,不动页序);已有 Slides 的多页大改优先用 [`+replace-pages`](references/lark-slides-replace-pages.md) 在原 presentation 内批量重建页面,避免 `slides +create` 生成新链接。选择 action 和完整读-改-写流程见 [`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)。
|
||||
**编辑已有幻灯片页面**:模板二创、页面级重写、导入底稿改写优先用 [`+replace-pages`](references/lark-slides-replace-pages.md) 在原 presentation 内重建页面,避免 `slides +create` 生成新链接;单个标题、文本块、图片或局部元素才用 [`+replace-slide`](references/lark-slides-replace-slide.md)(块级替换/插入,不动页序)。选择 action 和完整读-改-写流程见 [`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)。
|
||||
|
||||
## 身份选择
|
||||
|
||||
@@ -74,10 +80,11 @@ lark-cli auth login --domain slides
|
||||
高频只读:
|
||||
|
||||
- [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md)
|
||||
- [planning-layer.md](references/planning-layer.md)(新建 / 大幅改写)
|
||||
- [visual-planning.md](references/visual-planning.md)(新建 / 大幅改写)
|
||||
- [asset-planning.md](references/asset-planning.md)(新建 / 大幅改写)
|
||||
- [validation-checklist.md](references/validation-checklist.md)(创建 / 大幅改写后)
|
||||
- [planning-layer.md](references/planning-layer.md)(Create Workflow:新建 / 从空白生成 / 明确重设计)
|
||||
- [visual-planning.md](references/visual-planning.md)(Create Workflow)
|
||||
- [asset-planning.md](references/asset-planning.md)(Create Workflow)
|
||||
- [template-rewrite-workflow.md](references/template-rewrite-workflow.md)(Template Rewrite Workflow:PPTX/PDF/slides/existing Slides 二创)
|
||||
- [validation-checklist.md](references/validation-checklist.md)(创建 / Template Rewrite / 大幅改写后)
|
||||
|
||||
按需再读:
|
||||
|
||||
@@ -87,11 +94,63 @@ lark-cli auth login --domain slides
|
||||
- 图片:[`lark-slides-media-upload.md`](references/lark-slides-media-upload.md)
|
||||
- 流程图 / 时序图 / 架构图 / 装饰图案:[`lark-slides-whiteboard.md`](references/lark-slides-whiteboard.md)
|
||||
- 图标:[`iconpark.md`](references/iconpark.md)、[`scripts/iconpark_tool.py`](scripts/iconpark_tool.py)
|
||||
- 模板:[`template-catalog.md`](references/template-catalog.md)、[`scripts/template_tool.py`](scripts/template_tool.py)
|
||||
- 内置模板(仅无用户材料时):[`template-catalog.md`](references/template-catalog.md)、[`scripts/template_tool.py`](scripts/template_tool.py)
|
||||
- 排障:[`troubleshooting.md`](references/troubleshooting.md)
|
||||
- 完整协议:[`slides_xml_schema_definition.xml`](references/slides_xml_schema_definition.xml)
|
||||
|
||||
## Workflow
|
||||
## Workflow Routing
|
||||
|
||||
### A. Create Workflow
|
||||
|
||||
适用:
|
||||
|
||||
- 新建 PPT。
|
||||
- 从空白生成。
|
||||
- 用户明确要求重新设计。
|
||||
- 没有模板保留诉求。
|
||||
|
||||
路由:
|
||||
|
||||
- 读取 `planning-layer.md`。
|
||||
- 可读取 `visual-planning.md`。
|
||||
- 可读取 `asset-planning.md`。
|
||||
- 生成 `slide_plan.json`。
|
||||
- 使用 `slides +create` 或对应创建流程。
|
||||
|
||||
### B. Template Rewrite Workflow
|
||||
|
||||
适用:
|
||||
|
||||
- 用户上传 PPTX / PDF / slides。
|
||||
- 用户给 existing Slides。
|
||||
- 用户说“基于这个模板”。
|
||||
- 用户说“保留原版式”。
|
||||
- 用户说“根据这个底稿生成”。
|
||||
- 用户说“二次创作 / 改写这个 PPT”。
|
||||
|
||||
路由:
|
||||
|
||||
- 读取 `template-rewrite-workflow.md`。
|
||||
- 不读取 `planning-layer.md`。
|
||||
- 不读取 `visual-planning.md`。
|
||||
- 不读取 `asset-planning.md`。
|
||||
- 不生成 `slide_plan.json`。
|
||||
- 不生成任何 rewrite plan / manifest。
|
||||
- 固定执行:import/readback -> `source.xml` -> `pages.json` -> `replace-pages` -> readback validation。
|
||||
- 不用 `python-pptx` 清空模板页、`add_slide(blank)` 重画,或导入新生成的本地 PPTX 作为最终产物。
|
||||
- 逐页从源页骨架向外改写,把新内容贴回源页已有容器和视觉节点;不能用一套通用卡片层覆盖模板结构。
|
||||
|
||||
### C. Switch Back To Create Workflow
|
||||
|
||||
只有用户明确表达以下意图,才允许从 Template Rewrite 切回 Create Workflow:
|
||||
|
||||
- “不要保留模板素材”
|
||||
- “只参考风格重做”
|
||||
- “重新设计整套 PPT”
|
||||
- “原模板只是灵感”
|
||||
- “完全换一种版式”
|
||||
|
||||
## Create Workflow
|
||||
|
||||
> **这是演示文稿,不是文档。** 每页 slide 是独立的视觉画面,信息密度要低,排版要留白。
|
||||
|
||||
@@ -99,7 +158,7 @@ lark-cli auth login --domain slides
|
||||
|
||||
不要生成无设计感的幻灯片。纯白背景 + 标题 + bullets 只能作为极简临时稿,不能作为正式交付。
|
||||
|
||||
开始写 XML 前,先在 `slide_plan.json` 里确定 deck 级视觉策略:
|
||||
以下设计规划只适用于 Create Workflow。开始写 XML 前,先在 `slide_plan.json` 里确定 deck 级视觉策略:
|
||||
|
||||
- **主题化配色**:配色必须服务本次主题、行业和受众,不要默认蓝色商务风。如果把同一套颜色换到另一个完全不同主题仍然成立,说明配色不够具体。
|
||||
- **主次比例**:选择 1 个主色承担约 60-70% 视觉权重,1-2 个辅助色承担结构和分区,1 个强调色只用于关键数字、结论或行动点。不要让所有颜色权重相同。
|
||||
@@ -133,7 +192,7 @@ lark-cli auth login --domain slides
|
||||
- 不要把素材缺失表现为空白图片框;必须按 `fallback_if_missing` 生成 XML-native 视觉。
|
||||
- 不要留下模板占位文案、示例公司名、示例日期或与用户主题无关的原模板内容。
|
||||
|
||||
### 创建方式选择
|
||||
### Create Workflow 创建方式选择
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|------|----------|
|
||||
@@ -147,9 +206,9 @@ lark-cli auth login --domain slides
|
||||
> [!IMPORTANT]
|
||||
> `slides +create --slides` 底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
|
||||
|
||||
### 模板与脚本优先流程
|
||||
### 内置模板与脚本优先流程
|
||||
|
||||
模板细则见 [template-catalog.md](references/template-catalog.md)。主流程只记住:先 `search`,锁定后 `summarize`,需要骨架时才 `extract`;不要直接读取完整模板 XML 或照搬占位文案。
|
||||
仅在用户没有提供本地/在线模板材料时使用内置模板流程。模板细则见 [template-catalog.md](references/template-catalog.md)。主流程只记住:先 `search`,锁定后 `summarize`,需要骨架时才 `extract`;不要直接读取完整模板 XML 或照搬占位文案。
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides/scripts/template_tool.py search --query "<用户需求原文>" --limit 3
|
||||
@@ -159,12 +218,12 @@ python3 skills/lark-slides/scripts/template_tool.py extract --template <template
|
||||
|
||||
```text
|
||||
Step 1: 需求澄清 & 读取知识
|
||||
- 澄清主题、受众、页数、风格;模板需求按“模板与脚本优先流程”处理
|
||||
- 读取 xml-schema-quick-ref.md;新建 / 大幅改写时还要读取 planning-layer.md、visual-planning.md、asset-planning.md
|
||||
- 澄清主题、受众、页数、风格;没有用户提供模板材料时,模板需求按“内置模板与脚本优先流程”处理
|
||||
- 读取 xml-schema-quick-ref.md;Create Workflow 还要读取 planning-layer.md、visual-planning.md、asset-planning.md
|
||||
|
||||
Step 2: 生成大纲 → 用户确认 → 写入 slide_plan.json
|
||||
- 生成结构化大纲供用户确认;如使用模板,标明基于哪个模板改写
|
||||
- 新建 / 大幅改写必须先创建目录并写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`
|
||||
- 生成结构化大纲供用户确认;如使用内置模板,标明基于哪个模板改写
|
||||
- Create Workflow 必须先创建目录并写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`
|
||||
- plan 字段、路径命名、模板边界和 `asset_need` 结构按 planning-layer.md / asset-planning.md 执行
|
||||
|
||||
Step 3: 按 slide_plan.json 生成 XML → 创建
|
||||
@@ -174,7 +233,7 @@ Step 3: 按 slide_plan.json 生成 XML → 创建
|
||||
|
||||
Step 4: 审查 & 交付
|
||||
- 创建完成后,必须用 xml_presentations.get 读取全文 XML,并按 validation-checklist.md 做显式验证记录,包括 XML 文本重叠检查
|
||||
- 失败或部分成功按 troubleshooting.md 处理;局部问题优先用 `+replace-slide` 修正
|
||||
- 失败或部分成功按 troubleshooting.md 处理;局部问题可用 `+replace-slide` 修正,页面级问题用对应页面流程修正
|
||||
- 没问题 → 交付:告知用户演示文稿 ID 和访问方式
|
||||
```
|
||||
|
||||
@@ -268,7 +327,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli slides +<verb> [flags]`
|
||||
| [`+create`](references/lark-slides-create.md) | 创建 PPT(可选 `--slides` 一步添加页面,支持 `<img src="@./local.png">` 占位符自动上传) |
|
||||
| [`+media-upload`](references/lark-slides-media-upload.md) | 上传本地图片到指定演示文稿,返回 `file_token`(用作 `<img src="...">`),最大 20 MB |
|
||||
| [`+replace-slide`](references/lark-slides-replace-slide.md) | 对已有幻灯片页面进行块级替换/插入(`block_replace` / `block_insert`),自动注入 id 和 `<content/>`,不改变页序 |
|
||||
| [`+replace-pages`](references/lark-slides-replace-pages.md) | 在原演示文稿内批量重建多个页面:先创建新页到旧页前,再删除旧页;适合已有 Slides 的多页大改,不新建链接 |
|
||||
| [`+replace-pages`](references/lark-slides-replace-pages.md) | 在原演示文稿内批量重建一个或多个页面:先创建新页到旧页前,再删除旧页;适合模板二创、页面级重写和素材保留,不新建链接 |
|
||||
|
||||
没有 Shortcut 覆盖时使用原生 API。高频资源:`xml_presentations.get` 读取全文;`xml_presentation.slide.create/delete/get/replace` 管理单页。
|
||||
|
||||
@@ -281,13 +340,17 @@ lark-cli slides <resource> <method> [flags] # 调用 API
|
||||
|
||||
## 核心规则
|
||||
|
||||
1. **先规划再写 XML**:新建演示文稿或大幅改写页面时,必须先写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`;模板、风格和大纲只能作为规划输入,不能绕过规划层
|
||||
2. **创建流程**:简单短 XML(1-3 页、结构简单、特殊字符少)可用 `slides +create --slides '[...]'` 一步创建;复杂内容、含图片/中文大段文本/嵌套引号/较多特殊字符,或超过 10 页时,默认先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide.create` 逐页添加
|
||||
3. **`<slide>` 直接子元素只有 `<style>`、`<data>`、`<note>`**:文本和图形必须放在 `<data>` 内
|
||||
4. **文本通过 `<content>` 表达**:必须用 `<content><p>...</p></content>`,不能把文字直接写在 shape 内
|
||||
5. **保存关键 ID**:后续操作需要 `xml_presentation_id`、`slide_id`、`revision_id`
|
||||
6. **删除谨慎**:删除操作不可逆,且至少保留一页幻灯片
|
||||
7. **编辑已有页面优先原链接更新**:修改单个 shape/img 用 `+replace-slide`(`block_replace` / `block_insert`),不要整页重建;已有 Slides 的多页整页重建用 `+replace-pages`,不要用 `slides +create` 新建整份 PPT;只有没有 shortcut 覆盖的特殊单页整页操作才手动 `slide.create` + `slide.delete`
|
||||
8. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传或 `+create --slides` 的 `@./path` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
|
||||
1. **先判定工作流**:新建/空白生成/明确重设计走 Create Workflow;PPTX/PDF/slides/existing Slides 模板二创走 Template Rewrite Workflow。不要把二创塞回 `slide_plan.json` 工作流。
|
||||
2. **Create Workflow 先规划再写 XML**:必须先写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`;内置模板、风格和大纲只能作为规划输入,不能绕过规划层。
|
||||
3. **Template Rewrite 不生成 plan artifact**:唯一事实源是 `source.xml`,唯一执行输入是 `pages.json`,替换后用 `readback.xml` 验证。默认 preserve source, replace content, local adjustment only。
|
||||
4. **Template Rewrite 不能本地清空重画**:禁止用 `python-pptx` 删除模板页、从 blank layout 重建、只借用尺寸/主题色后生成本地 PPTX 再导入。`source.xml` 不可得时停止,不要伪装成模板二创。
|
||||
5. **Template Rewrite 不能通用卡片覆盖模板**:新内容必须映射到源页已有文本框、图形标签、节点、箭头、时间线、图表/table 或注释容器。源页 dominant structure 仍要承担表达,不允许只把它留在背景里。
|
||||
6. **创建流程**:简单短 XML(1-3 页、结构简单、特殊字符少)可用 `slides +create --slides '[...]'` 一步创建;复杂内容、含图片/中文大段文本/嵌套引号/较多特殊字符,或超过 10 页时,默认先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide.create` 逐页添加
|
||||
7. **`<slide>` 直接子元素只有 `<style>`、`<data>`、`<note>`**:文本和图形必须放在 `<data>` 内
|
||||
8. **文本通过 `<content>` 表达**:必须用 `<content><p>...</p></content>`,不能把文字直接写在 shape 内
|
||||
9. **保存关键 ID**:后续操作需要 `xml_presentation_id`、`slide_id`、`revision_id`
|
||||
10. **删除谨慎**:删除操作不可逆,且至少保留一页幻灯片
|
||||
11. **编辑已有页面优先原链接更新**:模板二创、页面级重写、素材保留用 `+replace-pages`;修改单个 shape/img/text block 才用 `+replace-slide`(`block_replace` / `block_insert`)。不要用 `slides +create` 新建脱离模板的 deck。
|
||||
12. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传或 `+create --slides` 的 `@./path` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果 `file_token` 来自同一个 `xml_presentation_id` 的旧页,可以在 Template Rewrite 的新页 XML 中直接复用;如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
|
||||
|
||||
> **注意**:如果 md 内容与 `slides_xml_schema_definition.xml` 或 `lark-cli schema slides.<resource>.<method>` 输出不一致,以后两者为准。
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Asset Planning
|
||||
|
||||
新建演示文稿或大幅改写页面时,在写入 `slide_plan.json` 前后都可以参考本文件。目标是让 agent 主动识别有价值的图、图标、图表、流程图、时序图、架构图、装饰图案、截图或示意图需求,同时保持 deck 在没有真实素材时也能完整执行。
|
||||
本文件默认只供 Create Workflow / `planning-layer.md` 使用。新建演示文稿、从零大幅改写或用户明确要求重设计时,在写入 `slide_plan.json` 前后都可以参考本文件。目标是让 agent 主动识别有价值的图、图标、图表、流程图、时序图、架构图、装饰图案、截图或示意图需求,同时保持 deck 在没有真实素材时也能完整执行。
|
||||
|
||||
本文件只定义轻量资产规划。不要把它理解成素材采集流程。
|
||||
|
||||
模板二创不要为了 `asset_need` 重新规划或替换旧素材。模板二创中的旧素材、旧容器、旧样式以 `source.xml` 为准,由 `template-rewrite-workflow.md` 处理。
|
||||
|
||||
## Core Rules
|
||||
|
||||
- `asset_need` is metadata only. It can guide page design, but it must not require web search, local download, media upload, or external tools.
|
||||
@@ -12,6 +14,8 @@
|
||||
- Prefer a few high-value asset plans over one asset on every page. For a 6-page technical or business deck, plan assets on at least 3 pages when the content allows.
|
||||
- If a real local asset already exists or the user provides one, it can be used through the normal media-upload workflow. Still keep `fallback_if_missing` in the plan.
|
||||
- Do not leave blank image boxes in final XML. If the asset is missing, render the fallback visual.
|
||||
- In Template Rewrite Workflow, `fallback_if_missing` cannot cover, replace, or obscure existing source img/table/chart/whiteboard/shape/motif/text containers.
|
||||
- In Template Rewrite Workflow, reference fallback ideas only when the source page has no usable carrying area and a new visual element is truly required.
|
||||
|
||||
## JSON Shape
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
局部编辑走 **shortcut [`+replace-slide`](lark-slides-replace-slide.md)**(块级替换 / 插入),配合 `xml_presentation.slide.get` 读原页拿 `block_id`。已有 Slides 的多页整页重建走 **[`+replace-pages`](lark-slides-replace-pages.md)**,保持原 presentation 链接不变。
|
||||
|
||||
模板/底稿/PPTX/PDF/existing Slides 二创必须先读 [`template-rewrite-workflow.md`](template-rewrite-workflow.md):以 `source.xml` 为源页骨架生成 replacement slide,不允许用 `python-pptx` 清空模板页、从 blank layout 重画、再导入新本地 PPTX 作为最终产物;也不允许把模板当背景后覆盖一套通用卡片层。
|
||||
|
||||
> 生成 XML 前**必读** [xml-schema-quick-ref.md](xml-schema-quick-ref.md)。
|
||||
|
||||
## 决策树:block_replace vs block_insert
|
||||
@@ -11,7 +13,7 @@
|
||||
| 已知某块的 `block_id`,要换这块内容(改标题、换图、挪坐标) | `block_replace` | 精准替换,原子性好;`replacement` 根 `id` 由 CLI 自动注入为 `block_id` |
|
||||
| 只加 1~N 个元素、不动现有布局 | `block_insert` | 新增不覆盖,可选 `insert_before_block_id` 指定位置 |
|
||||
| 一次动多个元素(如:换标题 + 加图) | 单次 `--parts` 里拼多条 | 整批作为原子事务,任一失败整批不生效;`block_replace` 和 `block_insert` 可混用 |
|
||||
| 多页版式重建、整页坐标重排 | `+replace-pages` | 原 presentation 内批量 create-before/delete-old,不生成新 Slides 链接 |
|
||||
| 模板二创、页面级改写、整页坐标调整 | `+replace-pages` | 原 presentation 内批量 create-before/delete-old;模板场景必须从源页 XML 骨架向外改,把新内容贴回源页已有容器、节点、箭头、时间线、图表/table 或注释,不生成新 Slides 链接 |
|
||||
|
||||
> **没有字段级 patch**:即便只想改一个 `shape` 的 `topLeftX`,也得把整个块的新 XML 写出来用 `block_replace`。这不是"微调",是块级重写。
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# slides +replace-pages(多页整页重建)
|
||||
|
||||
批量替换已有演示文稿里的多个页面,保持原 `xml_presentation_id` 和原 Slides 链接不变。适合多页版式大改、坐标重排、整页视觉重建;单个文本框、图片或 shape 的局部编辑仍优先用 [`+replace-slide`](lark-slides-replace-slide.md)。
|
||||
批量替换已有演示文稿里的多个页面,保持原 `xml_presentation_id` 和原 Slides 链接不变。适合模板二创、页面级改写、坐标调整和素材保留;单个文本框、图片或 shape 的局部编辑仍优先用 [`+replace-slide`](lark-slides-replace-slide.md)。
|
||||
|
||||
> 重要:这是多步编排,不是后端原子事务。CLI 对每页执行“先创建新页到旧页前,再删除旧页”;创建失败时旧页会保留。删除失败时可能出现新旧页同时存在,需要按返回结果继续处理。
|
||||
|
||||
> 模板二创重要边界:`+replace-pages` 消费完整 replacement slide XML,但这个 XML 应以 `source.xml` 的源页结构为骨架。不要用 `python-pptx` 清空模板页、从 blank layout 重画、生成新本地 PPTX 再导入来替代本命令;也不要把模板当背景,再覆盖一套通用卡片系统。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
@@ -42,6 +44,8 @@ lark-cli slides +replace-pages \
|
||||
|
||||
- 每项必须提供 `slide_id`;不支持 `slide_number`。
|
||||
- `content` 必须是完整 `<slide>...</slide>` XML。
|
||||
- 模板二创时,`content` 应复用源页 `<style>`、`<img src>`、chart/table/whiteboard、shape、line/icon、文本容器等结构,只替换必要文本或局部元素。
|
||||
- 模板二创时,新内容应贴回源页已有 text container、图形标签、节点、箭头、时间线、chart/table 或注释容器;源页的 dominant structure 不能只留作背景装饰。
|
||||
- 同一批次不能重复 `slide_id`。
|
||||
- CLI 不会回读整份 presentation;如果 `slide_id` 已失效,create/delete 阶段会返回对应错误。
|
||||
|
||||
@@ -92,4 +96,5 @@ lark-cli slides +replace-pages --as user \
|
||||
1. 大幅改写前先 `xml_presentations.get` 保存当前 XML,并记录要替换页面的 `slide_id`。
|
||||
2. 生成只含 `slide_id` 的 `pages.json` 后先跑 `--dry-run` 或 `--validate-only`。
|
||||
3. 默认不要开 `--continue-on-error`,除非能接受部分页面已替换。
|
||||
4. 替换后再回读全文 XML 并截图检查,确认页序、视觉和文本没有破损。
|
||||
4. 模板二创不要把源页改成通用两卡、三卡、2x2 卡片,也不要用大白卡或大色块覆盖模板主体素材。源页如果有箭头、节点、时间线、图表、表格、几何结构、设备图或人物图,优先替换这些结构上的标签、数字和注释。
|
||||
5. 替换后再回读全文 XML,并按 `validation-checklist.md` 对比 `source.xml` 和 `readback.xml`;需要确认视觉一致性时,用 `slides +screenshot` 抽查封面、典型内容页、复杂结构页和结尾页。
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Planning Layer
|
||||
|
||||
新建演示文稿或大幅改写页面时,必须先写 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`,再生成 XML。这个文件是 deck 的设计中间层,用来把叙事、页面角色、布局、视觉重点和文字密度固定下来,避免从用户提示直接跳到 XML。
|
||||
本文件只适用于 Create Workflow:新建演示文稿、从零大幅改写、用户明确要求重设计,或没有模板保留诉求的场景。进入本工作流时,必须先写 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`,再生成 XML。这个文件是 deck 的设计中间层,用来把叙事、页面角色、布局、视觉重点和文字密度固定下来,避免从用户提示直接跳到 XML。
|
||||
|
||||
小型已有页编辑可豁免,例如只替换一个标题、改一个数字、插入一个块、上传并插入一张图。只要任务会重排多页、生成新 deck、替换整页结构,仍然需要规划层。
|
||||
如果用户提供 PPTX/PDF/slides 作为模板、底稿或二创对象,或要求保留原版式/素材/结构,请走 `template-rewrite-workflow.md`。不要在 `slide_plan.json` 工作流中处理模板二创。
|
||||
|
||||
小型已有页编辑可豁免,例如只替换一个标题、改一个数字、插入一个块、上传并插入一张图。模板二创也不使用本规划层;它以 `source.xml` 为事实源、`pages.json` 为执行输入。
|
||||
|
||||
## Required Flow
|
||||
|
||||
1. 理解用户需求,必要时澄清主题、受众、页数、风格。
|
||||
2. 如果适合模板,先用 `template_tool.py search` 检索,锁定模板后用 `summarize` 获取主题和页型信息。
|
||||
2. 如果没有用户提供本地/在线模板材料且适合内置模板,先用 `template_tool.py search` 检索,锁定模板后用 `summarize` 获取主题和页型信息。
|
||||
3. 选择唯一 plan 目录:`.lark-slides/plan/<deck-or-task-id>/`。
|
||||
4. 先创建目录:`mkdir -p .lark-slides/plan/<deck-or-task-id>`。
|
||||
5. 写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`。
|
||||
@@ -15,7 +17,9 @@
|
||||
7. 按 plan、visual planning 和 asset planning 规则逐页生成 XML,把 `layout_type`、`visual_focus`、`text_density` 转成具体页面几何和文本量约束,并把缺失素材转成可执行兜底视觉。
|
||||
8. 创建 PPT 后用 `xml_presentations.get` 回读,核对页面数量、关键元素和 plan 到 XML 的对应关系。
|
||||
|
||||
模板不能代替 plan。模板搜索和摘要只能影响 `theme_style`、页面流、布局选择和局部布局骨架;最终仍必须有 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`。
|
||||
内置模板不能代替 plan。模板搜索和摘要只能影响 `theme_style`、页面流、布局选择和局部布局骨架;最终仍必须有 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`。
|
||||
|
||||
如果用户提供 PPTX/PDF/slides 作为模板、底稿或二创对象,请走 `references/template-rewrite-workflow.md`。不要在本工作流中复制 `source.xml` 的素材清单、bbox、层级或样式,也不要生成 `page_rewrite_plan.json` / `rewrite_manifest.json`。
|
||||
|
||||
## Plan Path
|
||||
|
||||
@@ -24,7 +28,7 @@ Use a separate plan directory per deck or task so multiple presentations in the
|
||||
Recommended IDs:
|
||||
|
||||
- New deck before creation: title slug plus date/time, such as `q3-review-20260507-1805`.
|
||||
- Existing PPT rewrite: the `xml_presentation_id`.
|
||||
- Existing PPT redesign after the user explicitly abandons template preservation: the `xml_presentation_id` plus a short redesign slug.
|
||||
- Ambiguous or untitled task: short task slug plus date/time.
|
||||
|
||||
Rules:
|
||||
@@ -40,7 +44,7 @@ Rules:
|
||||
Keep:
|
||||
|
||||
- `.lark-slides/plan/<deck-or-task-id>/slide_plan.json` after successful creation or major rewrite. The plan is the editable design state for the deck.
|
||||
- A small manifest when useful for follow-up work, such as `xml_presentation_id`, slide IDs, `revision_id`, plan path, and verification status.
|
||||
- A small creation status note when useful for follow-up work, such as `xml_presentation_id`, slide IDs, `revision_id`, plan path, and verification status. Do not create a rewrite manifest for Template Rewrite Workflow.
|
||||
|
||||
Clean or avoid keeping:
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<xs:simpleType name="FontFamilyType">
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
字体族名称, 支持任意字体。
|
||||
字体族名称, 支持下列字体。
|
||||
|
||||
常用中文字体:
|
||||
思源宋体、寒蝉德黑体、标小智无界黑、寒蝉锦书宋、站酷小薇体、
|
||||
|
||||
218
skills/lark-slides/references/template-rewrite-workflow.md
Normal file
218
skills/lark-slides/references/template-rewrite-workflow.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Template Rewrite Workflow
|
||||
|
||||
本工作流只服务基于真实模板或底稿的二次创作。适用场景:
|
||||
|
||||
- 用户上传 PPTX / PDF / slides。
|
||||
- 用户给 existing Slides。
|
||||
- 用户说“基于这个模板生成”。
|
||||
- 用户说“保留这个版式 / 底稿 / 原 PPT / 模板风格和结构”。
|
||||
- 用户要对已有 PPT 做二次创作、改写、替换内容。
|
||||
|
||||
禁止默认行为:
|
||||
|
||||
- 不默认走 `planning-layer.md`。
|
||||
- 不默认读取 `visual-planning.md`。
|
||||
- 不默认读取 `asset-planning.md`。
|
||||
- 不生成 `slide_plan.json`。
|
||||
- 不生成 `page_rewrite_plan.json`。
|
||||
- 不生成 `rewrite_manifest.json`。
|
||||
- 不默认用 `slides +create` 新建脱离模板的 deck。
|
||||
- 不通过 `python-pptx` / PowerPoint 自动化把模板页全部删除后从空白 layout 重画。
|
||||
- 不生成一个“借用模板尺寸/主题色”的本地 PPTX 再导入,并声称它是模板二创。
|
||||
- 不把模板页当成背景板,再在上面覆盖一套通用标题栏、两卡、三卡、2x2 卡片或大白卡系统。
|
||||
- 不把每页内容粘贴进一套重复组件,而让源页的箭头、节点、时间线、图形、图表、留白关系失去表达作用。
|
||||
|
||||
模板二创的数据流固定为:
|
||||
|
||||
```text
|
||||
source.xml
|
||||
-> generate replacement slide XML from each source slide skeleton
|
||||
-> pages.json
|
||||
-> slides +replace-pages
|
||||
-> readback.xml validation
|
||||
```
|
||||
|
||||
## 1. Import / Readback
|
||||
|
||||
- PPTX 必须先导入为 Slides。导入命令本身走 `lark-drive` 的 `drive +import --type slides`,但导入后的二创由本工作流负责。
|
||||
- PDF 如果作为模板、底稿、原 PPT 或视觉参考使用,也先导入为 Slides;只有明显是长文档资料而非演示稿时才不进入本工作流。
|
||||
- Existing Slides 用 `xml_presentations.get` 回读。
|
||||
- 保存回读 XML 为:
|
||||
|
||||
```text
|
||||
.lark-slides/plan/<xml_presentation_id>/source.xml
|
||||
```
|
||||
|
||||
如果无法得到 `source.xml`,Template Rewrite Workflow 不能继续。不要退化为:
|
||||
|
||||
- 用 `python-pptx` 打开模板后删除所有原 slides。
|
||||
- `prs.part.drop_rel(...)` / `del prs.slides._sldIdLst[...]` 清空模板页。
|
||||
- 从 `prs.slide_layouts[...]` 或 blank layout 重新 `add_slide(...)`。
|
||||
- 只保留画布尺寸、少量主题色、少量母版占位符后重画整套内容页。
|
||||
- 输出一个新的本地 PPTX,再用 `drive +import --type slides` 当最终产物。
|
||||
|
||||
正确处理是:停止 Template Rewrite,说明 `source.xml` 不可用,并让用户选择导入失败排障、只交付原导入 deck,或明确切换到 Create Workflow / 只参考风格重做。
|
||||
|
||||
## 2. Treat source.xml As Truth
|
||||
|
||||
`source.xml` 是唯一布局和素材事实源。
|
||||
|
||||
- 不要把 `source.xml` 里的素材 token、bbox、层级、样式再复制到新的 plan 文件。
|
||||
- 不要让模型手写素材清单来替代 `source.xml`。
|
||||
- 所有保留判断以 `source.xml` 为准。
|
||||
- 不要用 `layout_type`、`visual_focus`、`visual_system` 来驱动模板二创。
|
||||
- 可以在上下文中临时分析每页的源结构,但不要把它保存成新的 JSON / Markdown plan artifact。
|
||||
|
||||
## 3. Rewrite From Source Outward
|
||||
|
||||
以源页 XML 为骨架生成 replacement slide。默认顺序:
|
||||
|
||||
1. 先复制源页的 `<style>`。
|
||||
2. 复制源页的 `<img src="...">`、`<chart>`、`<table>`、`<whiteboard>`。
|
||||
3. 复制 recurring shapes / motifs、line / icon / separator、card container、reusable text container。
|
||||
4. 识别源页中承载表达的 dominant structure,例如箭头流、节点关系、时间线、漏斗、三角形、圆环、曲线、坐标/表格、左右对照、设备图、人物/场景分组。
|
||||
5. 把新内容映射到源页已有文本容器、图形标签、数字标签、节点标签或图表/table 数据上。
|
||||
6. 替换旧文案所在文本容器里的 `<content>`。
|
||||
7. 最后只在必要时添加局部新元素。
|
||||
|
||||
不要把源页改成通用两卡、三卡、2x2 卡片。不要把“保留模板”简化成“保留背景图 + 重新画业务卡片”。
|
||||
|
||||
生成 replacement slide 时,页面级结构必须来自源页 XML。可以替换或缩短文字、更新图表数据、局部补充元素;不能把源页删除后按自定义 `rect()` / `circle()` / `line()` / `add_text()` helper 重新搭一套卡片、流程、指标版式。
|
||||
|
||||
### Source-Connected Rewrite
|
||||
|
||||
每一页必须先在工作上下文中做源页结构判断。这个判断不是新 artifact,不写入文件;它只用于约束生成:
|
||||
|
||||
- page role:封面、目录、过渡、数据页、流程页、对比页、总结页等。
|
||||
- dominant source structure:源页最主要的视觉结构,例如图、表、箭头、节点、时间线、几何结构、产品图、人物图、设备图、曲线或对比版式。
|
||||
- content-bearing containers:真正承载文字和数字的源文本框、图形标签、图表标签、表格单元格。
|
||||
- source visual hierarchy:标题、核心结论、主视觉、支撑信息、脚注的原始层级。
|
||||
- safe insertion zones:只有在源页没有合适容器且用户内容必须出现时,才可使用的局部空白区域。
|
||||
|
||||
生成 replacement slide 时必须满足:
|
||||
|
||||
1. 新文案优先进入已有 text container、图形标签、节点标签、数字标签、表格单元格或 chart labels / data。
|
||||
2. 如果源页有三角形、箭头、节点、时间线、曲线、地图、设备图、产品图或人物分组,新内容必须贴到这些源结构的对应标签/节点/注释上,而不是覆盖一组三张新卡片。
|
||||
3. 如果源页是数据图形页,优先更新原图表、数字标签、曲线节点、坐标标签和注释;不要另造一个白色数据卡片区遮住原图。
|
||||
4. 如果源页是流程/关系页,优先替换每个步骤、箭头、节点、关系说明;不要把流程压在背景里,再另起 bullet 卡片。
|
||||
5. 如果源页是封面或章节页,保留原图片、标题容器、logo / slogan / 装饰关系;不要把标题挪进不相干的新色块。
|
||||
6. 如果原文本容器空间不足,先缩短文案、降低层级、拆到邻近源容器或用源页已有注释容器承载;不要默认新增大卡片。
|
||||
7. 新增元素只能补足源结构的局部空缺,不能成为覆盖源结构的主版式。
|
||||
8. 多页之间应保留源模板原本的页型差异;不要把整套 deck 归一成同一套顶栏 + 三卡片。
|
||||
|
||||
### Source-Connectedness Gate
|
||||
|
||||
生成 `pages.json` 前,对每个 replacement slide 做一次失败门检查。出现以下任一情况,必须重写该页:
|
||||
|
||||
- 页面主体内容主要落在新增 shape/card 中,而不是源页已有容器或源结构节点里。
|
||||
- 源页的箭头、节点、时间线、图形、图表、设备图、人物图仍在,但已经只是背景装饰,没有承载新内容。
|
||||
- 新增卡片、白板、大色块或信息面板覆盖了源页 dominant structure。
|
||||
- 多个源页被改成同一套顶栏、三卡、2x2 卡片或大段 bullet 容器。
|
||||
- 页内关键源容器还在,但其 bbox、层级、字号、颜色、对齐关系被无理由改写。
|
||||
- 源页明明有图文关系、箭头关系或坐标关系,却把内容独立堆放到空白区域,导致互相错位或遮挡。
|
||||
|
||||
## 4. Generate pages.json
|
||||
|
||||
`pages.json` 是唯一执行输入。结构只保留 `slides +replace-pages` 需要的字段:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"slide_id": "<old slide id>",
|
||||
"content": "<full replacement slide XML>"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
不要把 planning metadata 放进 `pages.json`。
|
||||
|
||||
## 5. Execute replace-pages
|
||||
|
||||
- 默认用 `slides +replace-pages`。
|
||||
- `replace-pages` 消费 `pages.json`,不消费 `slide_plan.json`。
|
||||
- `replace-slide` 只用于小型块级编辑,例如改一个标题、插入一个图、替换已知 block。
|
||||
|
||||
## 6. Readback Validation
|
||||
|
||||
替换后必须用 `xml_presentations.get` 回读,保存为:
|
||||
|
||||
```text
|
||||
.lark-slides/plan/<xml_presentation_id>/readback.xml
|
||||
```
|
||||
|
||||
用 `readback.xml` 和 `source.xml` 对比验证模板结构没有被破坏。验证细则见 `validation-checklist.md` 的 Template Rewrite validation 小节。
|
||||
|
||||
## Preservation Rules
|
||||
|
||||
除非用户明确要求重设计,否则模板二创必须:
|
||||
|
||||
- preserve source layout
|
||||
- preserve source assets
|
||||
- preserve source style
|
||||
- preserve source text containers
|
||||
- preserve source visual hierarchy
|
||||
- replace content only
|
||||
- local adjustment only
|
||||
|
||||
具体规则:
|
||||
|
||||
1. `<style>` 默认保留。
|
||||
2. `<img src="...">` 默认保留,尤其是背景图、截图、装饰图、产品图、logo、模板视觉。
|
||||
3. 同一个 `xml_presentation_id` 内复用 `<img src>` 时,直接复制原 `src` / token,不要重新上传,不要替换成外部 URL。
|
||||
4. `<chart>` / `<table>` 默认保留;除非用户要求更新数据,才改 labels / data。
|
||||
5. `<whiteboard>` 默认保留其位置和外层结构;注意 readback XML 未必包含内部 SVG / Mermaid。
|
||||
6. shape / line / icon / separator / card container / motif 默认保留。
|
||||
7. 旧文案所在 text container 默认保留 bbox、layer、textType、fontFamily、fontSize、color、alignment,只替换 `<content>`。
|
||||
8. 如果源页已有卡片容器,优先复用源容器。
|
||||
9. 如果源页已有图文结构,优先替换原文本。
|
||||
10. 如果必须新增元素,新增元素必须局部且不破坏源页主要视觉结构。
|
||||
11. 不允许以“模板文件只是内容容器”为由清空原页;模板页本身就是必须保留的设计资产。
|
||||
12. 不允许把模板当成 wallpaper。源页的 dominant structure 必须继续承载内容和语义。
|
||||
|
||||
## Local PPTX Is Not A Rewrite Target
|
||||
|
||||
Template Rewrite 的写入目标是导入后或已有的 Slides presentation。默认最终写入动作是 `slides +replace-pages`,不是创建一个新的本地 PPTX。
|
||||
|
||||
禁止的本地 PPTX 生成模式:
|
||||
|
||||
```python
|
||||
while len(prs.slides._sldIdLst):
|
||||
r_id = prs.slides._sldIdLst[0].rId
|
||||
prs.part.drop_rel(r_id)
|
||||
del prs.slides._sldIdLst[0]
|
||||
|
||||
blank = prs.slide_layouts[...]
|
||||
slide = prs.slides.add_slide(blank)
|
||||
```
|
||||
|
||||
上面这种模式会删除背景图、截图、装饰图、产品图、logo、shape、文本框、层级和页内结构。它最多是 Create Workflow 的“新建 PPT”,不是模板二创。
|
||||
|
||||
## No Full-Page Wash / Mask
|
||||
|
||||
禁止默认添加:
|
||||
|
||||
- full-page wash
|
||||
- near-full-page overlay
|
||||
- 全页半透明白色蒙版
|
||||
- 全页半透明黑色蒙版
|
||||
- 覆盖页面主体区域的大矩形
|
||||
- `rgba(255,255,255,0.x)` 大面积遮罩
|
||||
- `rgba(0,0,0,0.x)` 大面积遮罩
|
||||
|
||||
原因:模板二创时,模板素材是优先保留对象。全页 wash 会视觉遮盖模板素材,即使 token 仍然存在,也等同于破坏模板。
|
||||
|
||||
允许的可读性增强仅包括:
|
||||
|
||||
- 局部 text backing
|
||||
- 局部 card backing
|
||||
- 调整文字颜色
|
||||
- 调整字重
|
||||
- 文字阴影
|
||||
- 缩短文案
|
||||
- 复用源页已有文本容器
|
||||
- 复制 `source.xml` 中原本存在的 overlay
|
||||
|
||||
如果新增 overlay 覆盖了大部分画布,应判定为失败,除非:
|
||||
|
||||
- 该 overlay 来自 `source.xml` 原有元素;或
|
||||
- 用户明确要求统一加蒙版 / 遮罩。
|
||||
@@ -23,6 +23,37 @@ lark-cli slides xml_presentations get --as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}'
|
||||
```
|
||||
|
||||
## Template Rewrite Validation
|
||||
|
||||
模板二创用 `slides +replace-pages` 后必须回读全文 XML,并保存:
|
||||
|
||||
```text
|
||||
.lark-slides/plan/<xml_presentation_id>/readback.xml
|
||||
```
|
||||
|
||||
同时对比同目录的 `source.xml`。通过标准:
|
||||
|
||||
1. `readback.xml` 中仍存在 `source.xml` 的关键 `<img src>` token。
|
||||
2. `readback.xml` 中仍存在关键 `<style>`、chart、table、whiteboard、shape motif、card container。
|
||||
3. 旧 text container 的 bbox、layer、font、color、alignment 没有无理由变化。
|
||||
4. 没有新增 full-page / near-full-page overlay、wash、mask。
|
||||
5. 没有把多页改成同质化两卡、三卡、2x2 卡片。
|
||||
6. 没有用大白卡、大色块覆盖模板主体素材。
|
||||
7. 没有把 source img 重新上传或替换成外部 URL。
|
||||
8. `pages.json` item 只包含 `slide_id` / `content`。
|
||||
9. `replace-pages` 使用 create-before-delete 语义时,确认最终页数正确。
|
||||
10. 如果发现模板素材 token 存在但被新增遮罩视觉遮盖,应判定为失败。
|
||||
11. 没有出现 `python-pptx` 清空模板页、blank layout 重建、本地生成 PPTX 再导入的路径。
|
||||
12. 源模板的媒体资产数量、关键图片 token、主要 shape/table/chart/whiteboard/text container 没有断崖式丢失。若原模板有大量媒体资产而结果只剩极少数媒体资产,应判定为失败,除非用户明确要求移除这些素材。
|
||||
13. 每页的 dominant source structure 仍然存在并承载内容,例如三角形、箭头、节点、时间线、曲线、图表、表格、设备图、人物图、产品图或左右对照结构没有退化成背景装饰。
|
||||
14. 新内容主要落在源页已有 text container、图形标签、节点标签、数字标签、chart/table 数据或源页注释容器里,而不是新增的通用卡片层里。
|
||||
15. 没有把多页改成同一套“顶栏 + 三卡片 / 2x2 卡片 / 大 bullet 面板”的重复版式。
|
||||
16. 源页有箭头流、节点关系、时间线、图形结构或坐标关系时,新文案与这些结构对齐,没有漂浮在不相关空白区域或互相遮挡。
|
||||
17. 能获取截图时,至少抽查封面、典型内容页、复杂结构页和结尾页;如果总页数不超过 8 页,逐页截图检查。截图验收重点是源模板视觉结构是否仍可见且承载内容。
|
||||
18. 验证记录必须说明“与 source.xml 的模板结构/素材保留对比结果”。只记录页数、关键词存在、`xml_text_overlap_lint.py error_count=0` 不足以通过 Template Rewrite validation。
|
||||
|
||||
允许的可读性增强仅限局部 text backing、局部 card backing、文字颜色/字重调整、文字阴影、缩短文案、复用源页已有文本容器,或复制 `source.xml` 中原本存在的 overlay。
|
||||
|
||||
## Automated XML Text Overlap Lint
|
||||
|
||||
回读 XML 保存到本地文件后,优先运行 XML 语法和文本重叠静态检查:
|
||||
@@ -36,6 +67,7 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
- `summary.error_count == 0`。任何 error 都必须先修复再交付。
|
||||
- 当前工具只检查 XML well-formed 和文本元素之间的明显重叠;它不检查越界、文本高度不足、图文压盖、表格/图表压盖或底部拥挤。
|
||||
- 该工具不能替代页数核对、关键内容核对或真实视觉验收。
|
||||
- 该工具不能验证模板视觉一致性。Template Rewrite Workflow 中,即使 `error_count == 0`,只要源页背景、图片、shape、文本框、结构层级或主要媒体资产被清空/重画/遮挡,仍然必须判定失败。
|
||||
|
||||
常见 code 的处理方向:
|
||||
|
||||
@@ -46,7 +78,7 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
|
||||
## Page Count And Structure
|
||||
|
||||
- 实际页数必须等于用户要求或 `slide_plan.json` 的页数。
|
||||
- 实际页数必须等于用户要求。Create Workflow 对照 `slide_plan.json`;Template Rewrite Workflow 对照 `source.xml` / `pages.json` 和 replace-pages 结果。
|
||||
- 如果创建过程部分失败,先记录已创建的 `xml_presentation_id`,再回读确认哪些页已写入。
|
||||
- 每页都应包含 `<data>`,且 `<data>` 内至少有一个非背景主体元素。
|
||||
- 封面、章节页、总结页可以文字较少,但不能只有空背景。
|
||||
@@ -54,7 +86,7 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
|
||||
## Expected Elements
|
||||
|
||||
按 `slide_plan.json` 和用户要求逐页核对:
|
||||
Create Workflow 按 `slide_plan.json` 和用户要求逐页核对:
|
||||
|
||||
- 标题或主结论存在,并能对应 `key_message`。
|
||||
- `layout_type` 对应的主要结构已生成。
|
||||
@@ -62,6 +94,15 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
- `text_density` 影响了文本量,没有用长 bullet 框替代规划。
|
||||
- `asset_need` 有真实素材时已放入正确区域;没有真实素材时,`fallback_if_missing` 已用 XML 形状、线条、标签、表格或图表兜底。
|
||||
|
||||
Template Rewrite Workflow 按 `source.xml`、`pages.json` 和用户替换要求逐页核对:
|
||||
|
||||
- 标题或主结论存在,并写入源页对应的标题/结论容器。
|
||||
- 源页 dominant structure 仍是页面中最醒目或最大的信息区域之一。
|
||||
- 新内容映射到源页已有文本容器、图形标签、节点、箭头、时间线、图表/table 或注释容器。
|
||||
- 源页原有图文关系、分组关系、层级关系仍然可读,没有被新增卡片层覆盖或挤散。
|
||||
- 多页之间保留源模板的页型差异,没有被统一改成同质化卡片页。
|
||||
- 当源容器装不下时,优先缩短文本、降低层级或复用邻近源容器;不能用大白卡、大色块或新面板覆盖模板主体。
|
||||
|
||||
如果用户指定了关键页,例如“架构解释”“Self-Attention 机制解释”“对比或演进视角”“总结页”,最终验证记录必须逐项说明这些页已存在。
|
||||
|
||||
## Blank Or Broken Page Signals
|
||||
@@ -72,6 +113,11 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
- 关键文本没有出现在回读 XML 中。
|
||||
- 图片仍是 `@./path`,或 `<img src>` 是 http(s) 外链。
|
||||
- 页面依赖的图片区域为空,且没有 fallback visual。
|
||||
- Template Rewrite 结果只保留模板尺寸/主题色,丢失大部分源页图片、背景、shape、文本容器或媒体资产。
|
||||
- Template Rewrite 结果由新生成本地 PPTX 导入,而不是对导入/已有 Slides 使用 `+replace-pages`。
|
||||
- Template Rewrite 结果把内容贴进新增通用卡片层,源页的箭头、节点、时间线、图表、几何结构、设备图或人物图只剩背景作用。
|
||||
- Template Rewrite 结果多页出现重复的顶栏、三卡片、2x2 卡片或大 bullet 面板,压过源模板原有页型差异。
|
||||
- Template Rewrite 结果中源页关键结构仍存在,但新内容没有贴回对应标签、节点、数字、表格或注释位置。
|
||||
- 返回 XML 缺页、页序明显错误,或某页内容被 shell 截断。
|
||||
- 大量形状坐标完全相同,导致主体内容重叠。
|
||||
- 渐变背景回退成空白或白底,导致文字不可读。
|
||||
@@ -94,6 +140,8 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
- 高密度页使用单个长 bullet list,没有分栏、表格或分组。
|
||||
- 标题、主视觉、正文的字号和颜色差异太弱,视觉层级不清。
|
||||
- 所有内容页都是同一套标题加 bullets 坐标。
|
||||
- Template Rewrite 中,新增元素漂浮在源结构上方,没有和源页图形、节点、表格、图片或注释形成对应关系。
|
||||
- Template Rewrite 中,源页主体视觉被新增白卡、色块、面板或文字框切断。
|
||||
|
||||
## Verification Record
|
||||
|
||||
@@ -105,6 +153,7 @@ python3 skills/lark-slides/scripts/xml_text_overlap_lint.py --input <presentatio
|
||||
- 关键页:架构解释 / Self-Attention / 对比或演进 / 总结页均存在。
|
||||
- 结构:检查了主要 shape/img/table/chart 元素,无明显空白页或破损页。
|
||||
- 布局:检查了标题层级、主视觉、重叠/越界/文本溢出风险。
|
||||
- 模板二创:逐页或抽样说明 source.xml 的 dominant structure 是否仍承载内容,是否存在通用卡片层覆盖源结构;如已截图,说明抽查页范围。
|
||||
```
|
||||
|
||||
不要声称完成了人工视觉验收,除非确实打开或获取了可视化结果。仅从 XML 静态检查得出的结论,应表述为“静态检查未发现明显问题”。
|
||||
|
||||
@@ -74,7 +74,7 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
||||
| `textAlign` | 文本对齐方式 |
|
||||
| `lineSpacing` | 行间距,schema 默认 `multiple:1.5` |
|
||||
| `fontSize` | 字号 |
|
||||
| `fontFamily` | 字体 |
|
||||
| `fontFamily` | 字体,必须来自 `slides_xml_schema_definition.xml` 的 `FontFamilyType` 清单 |
|
||||
| `color` | 字体颜色 |
|
||||
| `bold` / `italic` / `underline` / `strikethrough` | 文本样式 |
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ class XmlTextOverlapLintError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
FONT_FAMILY_PLACEHOLDER_VALUES = {"undefined"}
|
||||
_SUPPORTED_FONT_FAMILIES: set[str] | None = None
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise XmlTextOverlapLintError(message)
|
||||
|
||||
@@ -75,6 +79,81 @@ def xml_local_name(tag: str) -> str:
|
||||
return tag.rsplit("}", 1)[-1] if tag.startswith("{") else tag
|
||||
|
||||
|
||||
def schema_definition_path() -> Path:
|
||||
return Path(__file__).resolve().parents[1] / "references" / "slides_xml_schema_definition.xml"
|
||||
|
||||
|
||||
def extract_supported_font_families(schema_xml: str) -> set[str]:
|
||||
simple_type_match = re.search(
|
||||
r'<xs:simpleType\s+name="FontFamilyType">([\s\S]*?)</xs:simpleType>',
|
||||
schema_xml,
|
||||
)
|
||||
if simple_type_match is None:
|
||||
fail("FontFamilyType definition not found in slides XML schema")
|
||||
|
||||
documentation_match = re.search(
|
||||
r"<xs:documentation>([\s\S]*?)</xs:documentation>",
|
||||
simple_type_match.group(1),
|
||||
)
|
||||
if documentation_match is None:
|
||||
fail("FontFamilyType documentation not found in slides XML schema")
|
||||
|
||||
font_families: set[str] = set()
|
||||
for raw_line in documentation_match.group(1).splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("字体族名称") or line.endswith(":"):
|
||||
continue
|
||||
for font_family in re.split(r"[、,,]", line):
|
||||
font_family = font_family.strip()
|
||||
if font_family:
|
||||
font_families.add(font_family)
|
||||
|
||||
if not font_families:
|
||||
fail("FontFamilyType supported font list is empty")
|
||||
return font_families
|
||||
|
||||
|
||||
def supported_font_families() -> set[str]:
|
||||
global _SUPPORTED_FONT_FAMILIES
|
||||
if _SUPPORTED_FONT_FAMILIES is None:
|
||||
_SUPPORTED_FONT_FAMILIES = extract_supported_font_families(read_file(schema_definition_path()))
|
||||
return _SUPPORTED_FONT_FAMILIES
|
||||
|
||||
|
||||
def line_column_at_offset(source: str, offset: int) -> tuple[int, int]:
|
||||
line = source.count("\n", 0, offset) + 1
|
||||
line_start = source.rfind("\n", 0, offset)
|
||||
column = offset + 1 if line_start == -1 else offset - line_start
|
||||
return line, column
|
||||
|
||||
|
||||
def lint_font_families(xml: str) -> list[dict[str, Any]]:
|
||||
issues: list[dict[str, Any]] = []
|
||||
allowed_font_families = supported_font_families()
|
||||
for match in re.finditer(r"\bfontFamily\s*=\s*([\"'])(.*?)\1", xml):
|
||||
font_family = match.group(2).strip()
|
||||
if not font_family or font_family in FONT_FAMILY_PLACEHOLDER_VALUES:
|
||||
continue
|
||||
if font_family in allowed_font_families:
|
||||
continue
|
||||
line, column = line_column_at_offset(xml, match.start())
|
||||
issues.append(
|
||||
{
|
||||
"level": "error",
|
||||
"code": "unsupported_font_family",
|
||||
"message": f'fontFamily "{font_family}" is not supported',
|
||||
"line": line,
|
||||
"column": column,
|
||||
"fontFamily": font_family,
|
||||
"hint": (
|
||||
"Use a FontFamilyType value from slides_xml_schema_definition.xml, "
|
||||
"or omit fontFamily to use the default font."
|
||||
),
|
||||
}
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def extract_error_context(xml: str, line: int | None, column: int | None, radius: int = 40) -> str | None:
|
||||
if line is None or column is None:
|
||||
return None
|
||||
@@ -326,18 +405,24 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
presentation = parse_presentation(xml)
|
||||
global_issues = lint_font_families(xml)
|
||||
slides = [
|
||||
lint_slide(slide_xml, index + 1)
|
||||
for index, slide_xml in enumerate(presentation["slides"])
|
||||
]
|
||||
error_count = sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "error")
|
||||
warning_count = sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "warning")
|
||||
return {
|
||||
error_count = sum(1 for issue in global_issues if issue["level"] == "error")
|
||||
error_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "error")
|
||||
warning_count = sum(1 for issue in global_issues if issue["level"] == "warning")
|
||||
warning_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "warning")
|
||||
result = {
|
||||
"file": source_path,
|
||||
"slide_size": {"width": presentation["width"], "height": presentation["height"]},
|
||||
"summary": {"slide_count": len(slides), "error_count": error_count, "warning_count": warning_count},
|
||||
"slides": slides,
|
||||
}
|
||||
if global_issues:
|
||||
result["issues"] = global_issues
|
||||
return result
|
||||
|
||||
|
||||
def print_usage() -> None:
|
||||
|
||||
@@ -110,6 +110,61 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(result["summary"]["error_count"], 0)
|
||||
|
||||
def test_lint_xml_accepts_supported_font_family(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
||||
<theme>
|
||||
<textStyles>
|
||||
<body fontFamily="思源黑体"/>
|
||||
</textStyles>
|
||||
</theme>
|
||||
<slide>
|
||||
<data>
|
||||
<shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
|
||||
<content textType="body" fontFamily="Inter"><p>Body text</p></content>
|
||||
</shape>
|
||||
</data>
|
||||
</slide>
|
||||
</presentation>
|
||||
"""
|
||||
)
|
||||
self.assertEqual(result["summary"]["error_count"], 0)
|
||||
self.assertNotIn("issues", result)
|
||||
|
||||
def test_lint_xml_allows_legacy_undefined_font_family_placeholder(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
||||
<data>
|
||||
<shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
|
||||
<content textType="body" fontFamily="undefined"><p>Body text</p></content>
|
||||
</shape>
|
||||
</data>
|
||||
</slide>
|
||||
"""
|
||||
)
|
||||
self.assertEqual(result["summary"]["error_count"], 0)
|
||||
self.assertNotIn("issues", result)
|
||||
|
||||
def test_lint_xml_reports_unsupported_font_family(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
||||
<data>
|
||||
<shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
|
||||
<content textType="body" fontFamily="微软雅黑"><p>Body text</p></content>
|
||||
</shape>
|
||||
</data>
|
||||
</slide>
|
||||
"""
|
||||
)
|
||||
issue = result["issues"][0]
|
||||
self.assertEqual(result["summary"]["error_count"], 1)
|
||||
self.assertEqual(issue["code"], "unsupported_font_family")
|
||||
self.assertEqual(issue["fontFamily"], "微软雅黑")
|
||||
self.assertIn("FontFamilyType", issue["hint"])
|
||||
|
||||
def test_lint_xml_single_slide_uses_default_canvas_without_bounds_checks(self) -> None:
|
||||
result = xml_text_overlap_lint.lint_xml(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user