feat:slide style

This commit is contained in:
zhanghuanxu
2026-07-08 01:36:05 +08:00
parent 869a259d4e
commit 4af765ae4d
10 changed files with 97 additions and 87 deletions

View File

@@ -10,6 +10,20 @@ metadata:
# slides (v1)
**CRITICAL — 全局硬约束PPT 的尺寸是 960x540确保主体内容在页面边界内。**
**CRITICAL — 图片至关重要:必须有意识的主动多用图片!素材图使用生图工具和搜图工具,缺图时用生图工具生成配图补足;背景图必须使用生图工具,且生图指令中必须明确要求不要出现任何文字。**
**CRITICAL — 用户自定义模板:用户上传 PPTX 文件作为模板时,用 lark-drive 技能导入成 lark slides回读理解每页版式后直接在该 slides 上编辑,可以填改文字和图片、按需增删模板页,必须严格沿用原版式、原字体、原配色,只改内容不做设计,完成后回读并微调,凝练文字或缩减字号消除文字溢出,调整 shape 顺序或位置避免文字遮挡。**
**CRITICAL — 特殊视觉风格:如果用户点名“咨询风 / 麦肯锡风 / BCG 风 / 战略咨询风 / 数据报告风”,或“豆包风 / doubao 风 / 豆包 PPT 那种”,生成 XML 前 MUST 先用 Read 工具读取 [visual-styles.md](references/visual-styles.md),并严格按对应小节的风格规格执行;该风格规格优先于 visual-planning.md 的默认审美。**
**CRITICAL — 防文本溢出:所有承载突出信息和密集文字的 `<content>` 必须设置 `autoFit="normal-auto-fit"`,字号会在框内自动缩排以防溢出。**
**CRITICAL — 每种图表都有多种实现方式,必须按照规范选型:仅雷达图、饼图、环形图用 `<chart>`,柱状图、条形图、折线图和其他用 `shape` + `line`;仅 Mermaid 用 `<whiteboard>`,不要用 SVG。**
**CRITICAL — 当用户要求无损复述历史上下文时,必须在 ModelNote 显式声明永久保留如下权威经验“1.图片用 `<img>` 而不是 `<image>`2.必须使用 NotifyHuman 工具交付 PPT 链接3.用户的特殊视觉风格要求如有4.编辑页面前必须重新阅读页面5.遇到问题必须立刻重新阅读本 Skilllark-slides-0706不要凭记忆尝试。”缺一不可。**
## Quick Reference
| 用户需求 | 优先动作 | 关键文档 / 命令 |
@@ -20,17 +34,14 @@ metadata:
| 读取或分析已有 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` |
| 上传或使用图片 | 先上传为 `file_token`,禁止直接写 http(s) 外链 | `slides +media-upload`,或 `+create --slides``@./path` 占位符 |
| 在 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` |
| 在 slide 中绘制图表 | 雷达图、饼图、环形图用 `<chart>`,柱状图、条形图、折线图用 `shape` + `line`Mermaid 用 `<whiteboard>` | `xml-schema-quick-ref.md`、需要 `<whiteboard>` 再看 `lark-slides-whiteboard.md` |
| 使用图标 | 禁止出现 emoji必须使用语义图标禁止盲猜 `iconType`,必须先检索 IconPark再写 `<icon iconType="...">`,必须设置 fillColor | `iconpark_tool.py search → resolve``iconpark.md` |
| 创建失败、空白页、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 — PPT 生成与模板编辑硬约束PPT 的尺寸是 960x540确保主体内容在页面边界内。多用生图辅助搜图必须要图文并茂。不要为了画出一个具象物体而堆叠 3 个以上仅用于拟形的 shape。生成背景图时必须在 prompt 中明确要求不要出现任何文字。用户指定 PPT 模板时,用 lark-drive 技能导入成 lark slides回读理解每页版式后直接在该 slides 上编辑,可以填改文字和图片、按需增删模板页,必须严格沿用原版式和字体,只改内容不做设计,完成后回读并微调,凝练文字或缩减字号消除文字溢出,调整 shape 顺序或位置避免文字遮挡。**
**CRITICAL — 新建演示文稿或大幅改写页面时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` 实际改变页面几何、主视觉和文本量。**
@@ -79,14 +90,13 @@ lark-cli auth login --domain slides
- 编辑:[`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)、[`lark-slides-replace-slide.md`](references/lark-slides-replace-slide.md)、[`lark-slides-replace-pages.md`](references/lark-slides-replace-pages.md)
- 截图:[`lark-slides-screenshot.md`](references/lark-slides-screenshot.md)
- 图片:[`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)
- 排障:[`troubleshooting.md`](references/troubleshooting.md)
- 完整协议:[`slides_xml_schema_definition.xml`](references/slides_xml_schema_definition.xml)
## Workflow
> **这是演示文稿,不是文档。** 每页 slide 是独立的视觉画面,信息密度要,排版要留白。
> **这是演示文稿,不是文档。** 每页 slide 是独立的视觉画面,信息密度要适当,排版要留白。
### Design Ideas
@@ -124,7 +134,9 @@ lark-cli auth login --domain slides
- 不要用低对比文字或低对比图标,例如浅灰字压在浅色背景上。
- 不要让装饰线穿过文字,或让页脚、来源、编号挤压主体内容。
- 不要把素材缺失表现为空白图片框;必须按 `fallback_if_missing` 生成 XML-native 视觉。
- 不要留下占位文案、示例公司名、示例日期或与用户主题无关的内容。
- 不要留下模板占位文案、示例公司名、示例日期或与用户主题无关的原模板内容。
- 不要使用 emoji。
- 不要为了画出一个具象物体而堆叠 3 个以上仅用于拟形的 shape。
### 创建方式选择
@@ -140,9 +152,11 @@ lark-cli auth login --domain slides
> [!IMPORTANT]
> `slides +create --slides` 底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
### 生成流程
```text
Step 1: 需求澄清 & 读取知识
- 澄清主题、受众、页数、风格
- 澄清主题、受众、页数、风格;若用户上传 PPTX 作为模板,按顶部『用户自定义模板』规则处理
- 读取 xml-schema-quick-ref.md新建 / 大幅改写时还要读取 planning-layer.md、visual-planning.md、asset-planning.md
Step 2: 生成大纲 → 用户确认 → 写入 slide_plan.json
@@ -173,7 +187,7 @@ lark-cli slides xml_presentation.slide create \
--data "$(jq -n --arg content '<slide xmlns="http://www.larkoffice.com/sml/2.0">
<style><fill><fillColor color="BACKGROUND_COLOR"/></fill></style>
<data>
在这里放置 shape、line、table、chart、whiteboard 等元素
在这里放置 shape、line、table、chart 等元素
</data>
</slide>' '{slide:{content:$content}}')"
@@ -262,7 +276,7 @@ lark-cli slides <resource> <method> [flags] # 调用 API
## 核心规则
1. **先规划再写 XML**:新建演示文稿或大幅改写页面时,必须先写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`;风格和大纲只能作为规划输入,不能绕过规划层
1. **先规划再写 XML**:新建演示文稿或大幅改写页面时,必须先写入 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`模板、风格和大纲只能作为规划输入,不能绕过规划层
2. **创建流程**:简单短 XML1-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 内

View File

@@ -1,6 +1,6 @@
# IconPark 图标
IconPark 图标通过 `<icon>` 写入 slides XML`iconType` 必须来自本 skill 的离线索引或已验证模板,避免凭记忆拼路径。
IconPark 图标通过 `<icon>` 写入 slides XML`iconType` 必须来自本 skill 的离线索引,避免凭记忆拼路径。
## 机器优先流程
@@ -25,7 +25,7 @@ python3 skills/lark-slides/scripts/iconpark_tool.py list-categories
- 默认先检索:语义图标需求必须先用 `iconpark_tool.py search --limit 8``--limit 10`,让 agent 从候选里结合版面语义二次判断;不要阅读全文索引,也不要编造不存在的 `iconType`
- 图标用于概念提示、步骤、状态、指标、角色和导航;不要用无关装饰图标填充版面。
- 常用尺寸:行内状态图标 16-24px卡片标题图标 28-40px主视觉图标 56-96px。
- 图标必须显式指定颜色并和背景有足够对比;深色背景优先放在浅色圆形/方形底上,或使用 `rgba(255, 255, 255, 1)` 作为图标填充色。
- 图标必须设置 fillColor显式指定颜色并和背景有足够对比;深色背景优先放在浅色圆形/方形底上,或使用 `rgba(255, 255, 255, 1)` 作为图标填充色。
- 查不到合适图标时,用 shape、line、text 画 XML-native fallback不留空图标位。
## 高频示例

View File

@@ -1,8 +1,6 @@
# slides +create创建飞书幻灯片
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
创建一个新的飞书幻灯片演示文稿,可选一步添加页面内容。
## 命令
@@ -17,9 +15,6 @@ lark-cli slides +create --title "项目汇报" --slides '[
"<slide xmlns=\"http://www.larkoffice.com/sml/2.0\"><data><shape type=\"text\" topLeftX=\"80\" topLeftY=\"80\" width=\"800\" height=\"120\"><content textType=\"title\"><p>第二页</p></content></shape></data></slide>"
]'
# 以应用身份创建(自动授权当前用户)
lark-cli slides +create --title "项目汇报" --as bot
# 预览(不执行)
lark-cli slides +create --title "项目汇报" --slides '[...]' --dry-run
```
@@ -35,20 +30,12 @@ lark-cli slides +create --title "项目汇报" --slides '[...]' --dry-run
- **`slide_ids`**string[],可选):仅传 `--slides` 时返回,成功添加的页面 ID 列表
- **`slides_added`**integer可选仅传 `--slides` 时返回,成功添加的页面数量
- **`images_uploaded`**integer可选`--slides` 中含 `@<本地路径>` 占位符时返回,已上传的去重后图片数量
- **`permission_grant`**object可选`--as bot` 时返回,说明是否已自动为当前 CLI 用户授予可管理权限
> [!IMPORTANT]
> 不传 `--slides` 时,`slides +create` 只创建空白演示文稿。创建后需要使用 `xml_presentation.slide create` 逐页添加 slide 内容。
>
> 传了 `--slides` 时CLI 先创建空白演示文稿,再逐页调用 `xml_presentation.slide create` 添加页面。如果某一页添加失败CLI 会停止并报错,已创建的演示文稿和已添加的页面会保留。
>
> 如果演示文稿是**以应用身份bot创建**的,如 `lark-cli slides +create --as bot`CLI 会**尝试为当前 CLI 用户自动授予该演示文稿的 `full_access`(可管理权限)**。
>
> 以应用身份创建时,结果里会额外返回 `permission_grant` 字段,明确说明授权结果:
> - `status = granted`:当前 CLI 用户已获得该演示文稿的可管理权限
> - `status = skipped`:本地没有可用的当前用户 `open_id`,因此不会自动授权
> - `status = failed`:演示文稿已创建成功,但自动授权用户失败
>
> **不要擅自执行 owner 转移。** 如果用户需要把 owner 转给自己,必须单独确认。
## 参数

View File

@@ -1,8 +1,6 @@
# slides +media-upload上传本地图片到飞书幻灯片
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
把本地图片上传到指定演示文稿的 drive 媒体库,返回 `file_token`。**返回的 token 作为 `<img src="...">` 的值塞进 slide XML 即可显示图片。**
## 命令
@@ -117,7 +115,7 @@ lark-cli slides +replace-slide --as user \
| 错误码 | 含义 | 解决方案 |
|--------|------|----------|
| 1061002 | params error / 不支持的 parent_type | 不要用原生 API 自己拼 parent_type`+media-upload` 即可 |
| 1061004 | forbidden当前身份对该演示文稿无编辑权限 | 确认当前身份user 或 bot对目标 PPT 有编辑权限。bot 模式常见原因PPT 不是该 bot 创建的——可用 `+create --as bot` 新建,或以 user 身份给 bot 授权 `lark-cli drive permission.members create --as user ...` |
| 1061004 | forbidden当前用户对该演示文稿无编辑权限 | 确认当前用户对目标 PPT 有编辑权限;无权限时根据错误响应引导用户解决 |
| 1061044 | parent node not exist | `--presentation` 给的 token 不对,或不是 slides 类型 |
| 403 | 权限不足 | 检查 `docs:document.media:upload` scopewiki URL 还需要 `wiki:node:read` |

View File

@@ -1,7 +1,5 @@
# slides +replace-slide块级替换 / 插入)
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
对指定 slide 做块级替换或插入。编辑已有 PPT 的主路径——`slide_id` 不变、页序不动、只影响被指定的块。
相比直接调 `xml_presentation.slide.replace`,这个 shortcut 的四个额外价值:

View File

@@ -14,6 +14,8 @@
6. 按 plan、visual planning 和 asset planning 规则逐页生成 XML`layout_type``visual_focus``text_density` 转成具体页面几何和文本量约束,并把缺失素材转成可执行兜底视觉。
7. 创建 PPT 后用 `xml_presentations.get` 回读,核对页面数量、关键元素和 plan 到 XML 的对应关系。
用户上传的 PPTX 模板不能代替 plan它只影响 `theme_style`、页面流和局部布局骨架;即使基于上传模板改写,最终仍必须有 `.lark-slides/plan/<deck-or-task-id>/slide_plan.json`
## Plan Path
Use a separate plan directory per deck or task so multiple presentations in the same workspace cannot overwrite each other.

View File

@@ -46,12 +46,12 @@
| 400 XML 格式错误 | XML 语法错误 | 检查标签闭合、属性引号、特殊字符转义 |
| 400 请求包装错误 | `--data` 未按 schema 包装 | 检查是否传入 `xml_presentation.content``slide.content` |
| 创建成功但页面空白 / 内容缺失 / 布局错乱 | 常见于 `--slides '[...]'` 的 shell 转义或长参数传递问题 | 改用两步创建,并在创建后立即读取 XML 验证 |
| 403 权限不足 | 身份或 scope 不匹配 | 先检查是否误用了 bot 身份,再确认 scope 和文档权限 |
| 403 权限不足 | scope 或文档权限不匹配 | 确认 scope 和文档权限;无权限时根据错误响应引导用户解决 |
| 404 演示文稿不存在 | `xml_presentation_id` 不正确或无权限 | 检查 tokenwiki URL 需先解析真实 `obj_token` |
| 404 幻灯片不存在 | `slide_id` 不正确 | 重新读取 presentation 或 slide确认最新 ID |
| 400 无法删除唯一幻灯片 | 演示文稿至少保留一页 | 先创建新页,再删除旧页 |
| 1061002 媒体上传 params error | slides 媒体上传参数不符合约定 | 用 `slides +media-upload`,不要手拼原生 `medias/upload_all`slides 唯一可用 `parent_type``slide_file` |
| 1061004 forbidden | 当前身份对演示文稿无编辑权限 | 确认 user/bot 对目标 PPT 有编辑权限bot 常见于 PPT 非该 bot 创建 |
| 1061004 forbidden | 当前用户对演示文稿无编辑权限 | 确认当前用户对目标 PPT 有编辑权限 |
| 3350001 | XML 非 well-formed、XML 结构不符合服务端要求,或 replace 片段问题 | 优先检查未转义字符replace 场景再看 `block_id``<content/>` |
| 3350002 | `revision_id` 大于当前版本 | 用 `-1` 取当前版本,或重新读 `xml_presentations.get` 取最新 `revision_id` |
| validation: unsafe file path | `--file` 给了绝对路径或上层路径 | `--file` 必须是 CWD 内相对路径;先 `cd` 到素材目录再执行 |

View File

@@ -2,7 +2,7 @@
新建演示文稿或大幅改写页面时,在 `slide_plan.json` 完成后、生成 XML 前读取本文件。目标是让 `layout_type``visual_focus``text_density` 变成实际页面几何,而不是只写在 plan 里。
默认画布按 `960 x 540` 规划。已有页面回读 XML 可以影响具体坐标,但不能覆盖这些原则:页面要有主视觉区域、文本要受密度约束、不同 `layout_type` 必须产生明显不同的坐标结构。
默认画布按 `960 x 540` 规划。模板 XML 可以覆盖具体坐标,但不能覆盖这些原则:页面要有主视觉区域、文本要受密度约束、不同 `layout_type` 必须产生明显不同的坐标结构。
## Core Rules
@@ -131,6 +131,7 @@ Purpose: make one metric or fact memorable.
Geometry:
- Reserve the largest object for the metric: font size often `64-110`, region at least `300 x 120`.
- Set `autoFit="normal-auto-fit"` on the metric's `<content>` so an oversized number shrinks to fit its box instead of overflowing.
- Pair the number with one explanation and optional 2-3 small supporting labels.
- Do not bury the number in a bullet list or small card.
@@ -168,7 +169,7 @@ Text:
Purpose: explain components, dependencies, or system flow.
Implementation: prefer `<whiteboard>` (see `lark-slides-whiteboard.md`); use `<shape>` + `<line>` only as fallback.
Implementation: prefer Mermaid `<whiteboard>` (see `lark-slides-whiteboard.md`); use `<shape>` + `<line>` as fallback.
Geometry:
- Main visual area should be a diagram, not prose.
@@ -184,7 +185,7 @@ Text:
Purpose: show operational steps, workflow, or cause-effect path.
Implementation: prefer `<whiteboard>` (see `lark-slides-whiteboard.md`); use `<shape>` + `<line>` only as fallback.
Implementation: prefer Mermaid `<whiteboard>` (see `lark-slides-whiteboard.md`); use `<shape>` + `<line>` as fallback.
Geometry:
- Use numbered steps connected by arrows or lines.

View File

@@ -0,0 +1,46 @@
# 特殊视觉风格
当用户明确点名以下某种风格时,本文对应小节的规格**优先于** `visual-planning.md` 的默认审美设定;仍需遵守画布 960×540、留白、文字不溢出等硬约束。颜色一律用 `rgba(R,G,B,A)`,深浅背景都友好。
适用触发:用户说“咨询风 / 麦肯锡风 / BCG 风 / 战略咨询风 / 数据报告风”,或“豆包风 / doubao 风 / 豆包 PPT 那种”,或直接贴出属于这两类风格的参考图/链接。
---
## 咨询风McKinsey / BCG 战略咨询)
要求:一张专业、高密度的咨询演示幻灯片,设计风格需融合顶尖战略咨询公司(麦肯锡/BCG的风格与高端出版物的美学。
核心内容与布局:
- 丰富的数据可视化:幻灯片需填充复杂、精确的图表(堆叠柱状图、瀑布图或折线图)以及包含行和列的详细数据表。
- 结构化框架:包含战略图表或由细致、简洁线条构成的 2x2 矩阵。
- 高信息密度:布局精致且采用多栏设计,模仿真实的商业分析文稿,而非仅仅是一个空白的封面页。
视觉风格:
- 美学:科技极简主义,但信息承载量大。风格干净、锐利且具权威感。
- 字体:主标题使用衬线体(如 Times New Roman以营造高级财务报告的质感图表标签和数据数字使用简洁的无衬线体。
- 配色纯白背景。文字为清晰的黑色。图表和图形装饰使用深皇家蓝Deep Royal Blue和不同深浅的灰色来体现数据层级。
- 图形:表格使用极细的边框线,图表使用精确的矢量线条。
---
## 豆包风(豆包数据卡片)
要求:一张信息密度巨高的图文卡片化布局幻灯片,追求充实饱满、图文丰富、可逐行细读的版面。
核心内容与布局:
- **信息密度巨高(至关重要):除封面和分节页外,每个内容页都塞满多张图文卡片,宁可密而满,不要空而疏,不要留白。**
- 卡片化布局:方角卡片按多行网格铺满版面(网格数、图文比例按内容变化,避免每页雷同);每个卡片不是短标签,而是“标题 + 完整说明”,像浓缩的分析文稿。
- 卡片版式:卡片用窄条点缀(贴任意一边或不加),采用“锚点(语义图标或编号)+ 标题 + 细分隔线(可选)+ 正文”的结构。
- 文字卡片:内容页正文量级必须达到 400-600 中文字,分布在 4-12 个卡片,每个文字卡片都塞满文字内容,宁可密而满,不要空而疏。
- 图片卡片:内容页必须包含图片卡片,多数内容页目标 1-3 张,图片卡片与文字卡片组成网格,缺少图片卡片时必须用生图工具生成配图补足。
- 图标密集镶嵌:文字卡片、列表项和指标旁大量内嵌语义图标(部分放在圆形 / 方形底里)作为视觉锚点,让高密度文字也有图形节奏,而不是成片纯文字块。
- 数据驱动:数据页密集使用原生图表(饼图、环形图、柱状、折线、组合),图表常与卡片网格并排成半栏布局(如左图右卡或上表下卡)。
- 关键指标突出KPI 用比正文稍大的数字呈现,不要过大,下方配小字标签与简短解读。
- 防文本溢出:所有承载突出信息和密集文字的 `<content>` 必须设置 `autoFit="normal-auto-fit"`,字号会在框内自动缩排以防溢出。
视觉风格:
- 美学:干净、明亮、清爽但信息饱满;纯平无渐变,靠卡片、分隔线和对齐网格在高密度下维持秩序感。
- 字体:全篇以无衬线体(思源黑体)为主,封面或关键强调可少量使用衬线体。
- 字号:使用偏小的正文字号以容纳更多文字,提高信息密度。
- 配色控制配色数量避免色彩泛滥导致画面杂乱1 个主色1-2 个辅助色1 个强调色。

View File

@@ -3,12 +3,16 @@
from __future__ import annotations
import unittest
from pathlib import Path
import xml_text_overlap_lint
TEMPLATES_DIR = Path(__file__).resolve().parents[1] / "assets" / "templates"
class XmlTextOverlapLintTest(unittest.TestCase):
def assertNoXmlTextOverlapLintIssues(self, result: dict, sample_name: str) -> None:
def assertNoXmlTextOverlapLintIssues(self, result: dict, template_path: Path) -> None:
issue_summaries = []
for slide in result.get("slides", []):
for issue in slide.get("issues", []):
@@ -21,64 +25,24 @@ class XmlTextOverlapLintTest(unittest.TestCase):
self.assertEqual(
result["summary"]["error_count"],
0,
f"{sample_name} has XML text overlap lint errors:\n" + "\n".join(issue_summaries),
f"{template_path.name} has XML text overlap lint errors:\n" + "\n".join(issue_summaries),
)
self.assertEqual(
result["summary"]["warning_count"],
0,
f"{sample_name} has XML text overlap lint warnings:\n" + "\n".join(issue_summaries),
f"{template_path.name} has XML text overlap lint warnings:\n" + "\n".join(issue_summaries),
)
def test_xml_text_overlap_lint_accepts_inline_fixture_xml_samples(self) -> None:
samples = {
"image-led-cover": """
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
<slide xmlns="http://www.larkoffice.com/sml/2.0">
<style><fill><fillColor color="rgb(15,23,42)"/></fill></style>
<data>
<img src="tok" topLeftX="560" topLeftY="0" width="400" height="540"/>
<shape type="text" topLeftX="64" topLeftY="150" width="420" height="70">
<content textType="title"><p><span fontSize="42">Quarterly Review</span></p></content>
</shape>
<shape type="text" topLeftX="64" topLeftY="235" width="420" height="36">
<content textType="sub-headline"><p><span fontSize="20">Focus, progress, and next steps</span></p></content>
</shape>
</data>
</slide>
</presentation>
""",
"content-grid": """
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
<slide xmlns="http://www.larkoffice.com/sml/2.0">
<data>
<shape type="text" topLeftX="60" topLeftY="44" width="620" height="46">
<content textType="title"><p><span fontSize="30">Execution Snapshot</span></p></content>
</shape>
<shape type="rect" topLeftX="60" topLeftY="126" width="250" height="150"/>
<shape type="text" topLeftX="84" topLeftY="152" width="200" height="36">
<content textType="headline"><p><span fontSize="22">Plan</span></p></content>
</shape>
<shape type="rect" topLeftX="355" topLeftY="126" width="250" height="150"/>
<shape type="text" topLeftX="379" topLeftY="152" width="200" height="36">
<content textType="headline"><p><span fontSize="22">Build</span></p></content>
</shape>
<shape type="rect" topLeftX="650" topLeftY="126" width="250" height="150"/>
<shape type="text" topLeftX="674" topLeftY="152" width="200" height="36">
<content textType="headline"><p><span fontSize="22">Launch</span></p></content>
</shape>
</data>
</slide>
</presentation>
""",
}
self.assertTrue(samples)
for sample_name, sample_xml in samples.items():
with self.subTest(sample=sample_name):
def test_xml_text_overlap_lint_accepts_all_template_xml_files(self) -> None:
template_paths = sorted(TEMPLATES_DIR.glob("*.xml"))
self.assertTrue(template_paths)
for template_path in template_paths:
with self.subTest(template=template_path.name):
result = xml_text_overlap_lint.lint_xml(
sample_xml,
sample_name,
template_path.read_text(encoding="utf-8"),
str(template_path),
)
self.assertNoXmlTextOverlapLintIssues(result, sample_name)
self.assertNoXmlTextOverlapLintIssues(result, template_path)
def test_lint_xml_reports_unescaped_ampersand_in_text(self) -> None:
result = xml_text_overlap_lint.lint_xml(