dsh-courseware 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/cordis.patch.yml +9 -0
- package/engine/pptxgen/__init__.py +8 -0
- package/engine/pptxgen/__main__.py +4 -0
- package/engine/pptxgen/builtin_template.py +218 -0
- package/engine/pptxgen/cli.py +575 -0
- package/engine/pptxgen/examples.py +290 -0
- package/engine/pptxgen/imagegen.py +564 -0
- package/engine/pptxgen/layoutmap.py +211 -0
- package/engine/pptxgen/patterns.py +106 -0
- package/engine/pptxgen/preview.py +126 -0
- package/engine/pptxgen/profile.py +456 -0
- package/engine/pptxgen/render.py +1673 -0
- package/engine/pptxgen/review.py +184 -0
- package/engine/pptxgen/schemacheck.py +122 -0
- package/engine/pptxgen/schemas/XAdES.xsd +466 -0
- package/engine/pptxgen/schemas/XAdESv141.xsd +15 -0
- package/engine/pptxgen/schemas/chartEx.xsd +838 -0
- package/engine/pptxgen/schemas/dml-chart.xsd +1499 -0
- package/engine/pptxgen/schemas/dml-chartDrawing.xsd +146 -0
- package/engine/pptxgen/schemas/dml-diagram.xsd +1085 -0
- package/engine/pptxgen/schemas/dml-drawing.xsd +63 -0
- package/engine/pptxgen/schemas/dml-lockedCanvas.xsd +11 -0
- package/engine/pptxgen/schemas/dml-main.xsd +3081 -0
- package/engine/pptxgen/schemas/dml-picture.xsd +23 -0
- package/engine/pptxgen/schemas/dml-spreadsheetDrawing.xsd +185 -0
- package/engine/pptxgen/schemas/dml-wordprocessingDrawing.xsd +287 -0
- package/engine/pptxgen/schemas/drawing-chart2012.xsd +129 -0
- package/engine/pptxgen/schemas/markup-compatibility.xsd +95 -0
- package/engine/pptxgen/schemas/opc-digSig.xsd +49 -0
- package/engine/pptxgen/schemas/opc-relationships.xsd +33 -0
- package/engine/pptxgen/schemas/pml.xsd +1676 -0
- package/engine/pptxgen/schemas/shared-additionalCharacteristics.xsd +28 -0
- package/engine/pptxgen/schemas/shared-bibliography.xsd +144 -0
- package/engine/pptxgen/schemas/shared-commonSimpleTypes.xsd +172 -0
- package/engine/pptxgen/schemas/shared-customXmlDataProperties.xsd +25 -0
- package/engine/pptxgen/schemas/shared-customXmlSchemaProperties.xsd +18 -0
- package/engine/pptxgen/schemas/shared-documentPropertiesCustom.xsd +59 -0
- package/engine/pptxgen/schemas/shared-documentPropertiesExtended.xsd +56 -0
- package/engine/pptxgen/schemas/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/engine/pptxgen/schemas/shared-math.xsd +582 -0
- package/engine/pptxgen/schemas/shared-relationshipReference.xsd +25 -0
- package/engine/pptxgen/schemas/signatureInfo.xsd +103 -0
- package/engine/pptxgen/schemas/sml.xsd +4439 -0
- package/engine/pptxgen/schemas/visio.xsd +829 -0
- package/engine/pptxgen/schemas/vml-main.xsd +570 -0
- package/engine/pptxgen/schemas/vml-officeDrawing.xsd +509 -0
- package/engine/pptxgen/schemas/vml-presentationDrawing.xsd +12 -0
- package/engine/pptxgen/schemas/vml-spreadsheetDrawing.xsd +108 -0
- package/engine/pptxgen/schemas/vml-wordprocessingDrawing.xsd +96 -0
- package/engine/pptxgen/schemas/vmlDrawing.xsd +36 -0
- package/engine/pptxgen/schemas/wml.xsd +3643 -0
- package/engine/pptxgen/schemas/word12.xsd +66 -0
- package/engine/pptxgen/schemas/xlThreadedComments.xsd +59 -0
- package/engine/pptxgen/schemas/xlThreadedComments2.xsd +22 -0
- package/engine/pptxgen/schemas/xmldsig-core-schema.xsd +318 -0
- package/engine/pptxgen/shapes.py +417 -0
- package/engine/pptxgen/spec.py +529 -0
- package/engine/pptxgen/template.py +834 -0
- package/engine/pptxgen/util.py +218 -0
- package/engine/pptxgen/xmlutil.py +298 -0
- package/engine/pptxgen.sh +7 -0
- package/engine/requirements.txt +9 -0
- package/lib/index.js +186 -0
- package/package.json +34 -0
- package/skills/courseware/SKILL.md +130 -0
- package/skills/courseware/references/content-polish.md +121 -0
- package/skills/courseware/references/deck-spec.md +155 -0
- package/skills/courseware/references/design-review.md +121 -0
- package/skills/courseware/references/image-backends.md +94 -0
- package/skills/courseware/references/quality-gates.md +71 -0
- package/skills/courseware/references/roadmap.md +55 -0
- package/skills/courseware/references/template-fidelity.md +100 -0
- package/skills/courseware/workflows/generate.md +172 -0
- package/skills/courseware/workflows/quick.md +41 -0
- package/skills/courseware/workflows/revise.md +40 -0
- package/skills/courseware/workflows/routing.md +49 -0
- package/skills/courseware/workflows/stages/generate-images.md +74 -0
- package/skills/courseware/workflows/template-intake.md +112 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liang Yu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# dsh-courseware
|
|
2
|
+
|
|
3
|
+
把 DeepSeek Harness 的 **courseware 技能**(中小学课件生成)及其 **pptxgen 引擎**
|
|
4
|
+
打包成官方可安装的 DSH 插件包:`dsh plugin --profile <名字> add <本包>`。
|
|
5
|
+
|
|
6
|
+
安装后,profile 里每个 agent 的技能目录都会出现 `courseware` 技能:
|
|
7
|
+
拿用户自己的 `.pptx` 模板 + 一份课程大纲(教案/文档/纯文本),产出套用该模板
|
|
8
|
+
背景、配色、字体、页面骨架的课件 `.pptx`。
|
|
9
|
+
|
|
10
|
+
## 包内结构
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
dsh-courseware/
|
|
14
|
+
├── package.json type:module + main:lib/index.js + dsh.bundle.patch(命门)
|
|
15
|
+
├── cordis.patch.yml 把本包插成一行,loader 才会加载它
|
|
16
|
+
├── lib/index.js 零依赖提供器:扫描 skills/ 注册进 ctx.skills
|
|
17
|
+
├── skills/courseware/ SKILL.md + workflows/ + references/
|
|
18
|
+
└── engine/ pptxgen 引擎源码 + pptxgen.sh + requirements.txt
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
前置:已装 DeepSeek Harness + pnpm(`dsh plugin` 内部调用 pnpm)。
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# 本地文件夹(pnpm 链接安装,改完重启 profile 即生效,适合开发)
|
|
27
|
+
dsh plugin --profile web add /路径/到/dsh-courseware
|
|
28
|
+
|
|
29
|
+
# 发布到 npm 后(推荐)
|
|
30
|
+
dsh plugin --profile web add dsh-courseware
|
|
31
|
+
|
|
32
|
+
# 或 GitHub / tarball
|
|
33
|
+
dsh plugin --profile web add https://github.com/你的账号/dsh-courseware.git
|
|
34
|
+
dsh plugin --profile web add /路径/to/dsh-courseware-0.1.0.tgz
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
安装后**重启 profile**(停掉重跑 `dsh web`,刷新浏览器),然后验证:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
dsh --profile web --dump-config # 输出里应出现 dsh-courseware 行
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
agent 的技能目录里应出现 `courseware`,可用 `skill` 工具加载。
|
|
44
|
+
|
|
45
|
+
卸载:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
dsh plugin --profile web remove dsh-courseware
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> 必须用 `dsh plugin` 形式安装。直接 `npm install` 只会把包当普通库装进当前
|
|
52
|
+
> 目录,不会注册进任何 profile,技能永远不会被加载。
|
|
53
|
+
|
|
54
|
+
## 首次使用:初始化引擎(一次性)
|
|
55
|
+
|
|
56
|
+
引擎需要 Python 3.10+,首次跑课件前初始化虚拟环境:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
python3 -m venv "<引擎目录>/.venv"
|
|
60
|
+
"<引擎目录>/.venv/bin/pip" install -r "<引擎目录>/requirements.txt"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
引擎目录 = 技能目录的上两级(agent 会自行按此规则定位);也可用环境变量
|
|
64
|
+
`PPTXGEN_ENGINE_HOME` 指向自备引擎。
|
|
65
|
+
|
|
66
|
+
另外,`check --render` 与 `preview` 需要本机装有 LibreOffice(macOS:
|
|
67
|
+
`/Applications/LibreOffice.app`);仅 `build` 不依赖它。模板用到的字体
|
|
68
|
+
(如 华文琥珀/微软雅黑)在目标机器上缺失时,字号会退化,建议装齐常用中文字体。
|
|
69
|
+
|
|
70
|
+
## 图片后端配置 —— 本包不含任何密钥
|
|
71
|
+
|
|
72
|
+
**包里没有任何 API key、也没有任何密钥配置**(引擎源码只有占位符;`~/.pptxgen/config.yaml`
|
|
73
|
+
在用户侧,不在包内)。每个使用者装好后自己配:
|
|
74
|
+
|
|
75
|
+
方式一:环境变量
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
export PPTXGEN_IMAGE_BASE_URL="https://你的服务商/v1"
|
|
79
|
+
export PPTXGEN_IMAGE_MODEL="你的模型名"
|
|
80
|
+
export PPTXGEN_IMAGE_API_KEY="你自己的 key"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
方式二:用户配置文件 `~/.pptxgen/config.yaml`
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
images:
|
|
87
|
+
base_url: https://你的服务商/v1
|
|
88
|
+
model: 你的模型名
|
|
89
|
+
api_key: 你自己的 key
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
引擎的图片配置优先级:环境变量 > 课件 `options.images` > 用户配置文件。
|
|
93
|
+
生成配图时 agent 会先检查后端是否配好;没配就停下来提示你去配(不静默跳过)。
|
|
94
|
+
|
|
95
|
+
## 发布到 npm 的注意
|
|
96
|
+
|
|
97
|
+
- `package.json` 的 `files` 白名单已排除 `.venv/`、`__pycache__/`、`.git/` 等;
|
|
98
|
+
发布前再跑一遍 `npm pack --dry-run` 确认包内**没有**任何密钥文件。
|
|
99
|
+
- 发布前先在干净的 profile 上本地安装验证一遍(上面"安装"节)。
|
|
100
|
+
- 包名 `dsh-courseware` 如与 npm 已有包冲突,可改成带 scope 的
|
|
101
|
+
`@你的账号/dsh-courseware`(改 package.json 的 `name` 与 cordis.patch.yml 的
|
|
102
|
+
`name` 两处即可)。
|
|
103
|
+
- 技能内容更新:改 `skills/courseware/` 后重新发布即可,无需改代码
|
|
104
|
+
(提供器自动发现新技能/新文件)。
|
|
105
|
+
- 引擎更新:替换 `engine/pptxgen/` 内容后重新发布。
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT。技能内容与引擎源码版权归原作者。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# dsh-courseware bundle patch: 把 courseware 技能提供器挂进 host 层技能注册表,
|
|
2
|
+
# 使 profile 里每个 agent preset 的作用域链都能合并它注册的技能。
|
|
3
|
+
#
|
|
4
|
+
# 本补丁叠加在 dsh-base 层之上;后面的层(profile 自己的 cordis.patch.yml、
|
|
5
|
+
# --patch 叠加)仍可按 id 定位这一行。技能正文在包内 skills/ 目录随包分发,
|
|
6
|
+
# 提供器用 import.meta.url 定位,无需任何用户配置。
|
|
7
|
+
- insert:
|
|
8
|
+
- id: dsh-courseware
|
|
9
|
+
name: 'dsh-courseware'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""pptxgen —— 基于自有模板的中小学课件 PPTX 生成器。"""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from .spec import SpecError, load_deck, normalize_deck # noqa: F401
|
|
6
|
+
from .template import load_template # noqa: F401
|
|
7
|
+
|
|
8
|
+
__all__ = ["load_deck", "load_template", "normalize_deck", "SpecError", "__version__"]
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""内置兜底模板:没有提供模板文件时,生成一份干净的 16:9 课件模板。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from typing import Dict, Optional
|
|
7
|
+
|
|
8
|
+
from pptx import Presentation
|
|
9
|
+
from pptx.enum.shapes import MSO_SHAPE
|
|
10
|
+
from pptx.oxml.ns import qn
|
|
11
|
+
from pptx.util import Emu, Inches, Pt
|
|
12
|
+
|
|
13
|
+
from .util import emu_from_inch, parse_color
|
|
14
|
+
|
|
15
|
+
DEFAULT_PALETTE: Dict[str, str] = {
|
|
16
|
+
"dk1": "1F2937",
|
|
17
|
+
"lt1": "FFFFFF",
|
|
18
|
+
"dk2": "5B6675",
|
|
19
|
+
"lt2": "EEF2F7",
|
|
20
|
+
"accent1": "2F6FED",
|
|
21
|
+
"accent2": "F08C00",
|
|
22
|
+
"accent3": "2F9E44",
|
|
23
|
+
"accent4": "7048E8",
|
|
24
|
+
"accent5": "0C8599",
|
|
25
|
+
"accent6": "E8590C",
|
|
26
|
+
"hlink": "2F6FED",
|
|
27
|
+
"folHlink": "7048E8",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
DEFAULT_MAJOR_FONT = "微软雅黑"
|
|
31
|
+
DEFAULT_MINOR_FONT = "微软雅黑"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_builtin_template(path: str, *, palette: Optional[Dict[str, str]] = None,
|
|
35
|
+
font: Optional[str] = None, theme_name: str = "DSH 课件模板",
|
|
36
|
+
size: str = "16:9") -> str:
|
|
37
|
+
prs = Presentation()
|
|
38
|
+
base_w = prs.slide_width
|
|
39
|
+
if size == "4:3":
|
|
40
|
+
# python-pptx 默认模板本身就是 4:3,无需缩放
|
|
41
|
+
pass
|
|
42
|
+
else:
|
|
43
|
+
prs.slide_width = Emu(12192000) # 13.333in
|
|
44
|
+
prs.slide_height = Emu(6858000) # 7.5in
|
|
45
|
+
|
|
46
|
+
pal = dict(DEFAULT_PALETTE)
|
|
47
|
+
if palette:
|
|
48
|
+
pal.update({k: parse_color(v) or v for k, v in palette.items()})
|
|
49
|
+
font = font or DEFAULT_MINOR_FONT
|
|
50
|
+
|
|
51
|
+
_patch_theme(prs, pal, font, theme_name)
|
|
52
|
+
if size != "4:3":
|
|
53
|
+
# 默认模板是 4:3 的版式,把占位符横向拉伸到目标比例
|
|
54
|
+
_rescale_horizontally(prs, prs.slide_width / float(base_w))
|
|
55
|
+
_patch_master(prs, pal)
|
|
56
|
+
title_pt = 36 if size != "4:3" else 32
|
|
57
|
+
body_pt = (20, 17, 15, 14, 13) if size != "4:3" else (24, 20, 18, 16, 14)
|
|
58
|
+
_set_txstyles(prs, title_pt=title_pt, body_pt=body_pt)
|
|
59
|
+
_rename_layouts(prs)
|
|
60
|
+
prs.save(path)
|
|
61
|
+
return path
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# 把 Office 默认版式改成中文语义名,让页型->版式映射天然精确
|
|
65
|
+
LAYOUT_NAMES = [
|
|
66
|
+
"封面", "标题和内容", "节标题", "两栏内容", "比较",
|
|
67
|
+
"仅标题", "空白", "内容与标题", "图片与标题", "标题和竖排文字", "竖排标题与文本",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _rename_layouts(prs) -> None:
|
|
72
|
+
for i, lay in enumerate(prs.slide_layouts):
|
|
73
|
+
if i < len(LAYOUT_NAMES):
|
|
74
|
+
try:
|
|
75
|
+
lay.name = LAYOUT_NAMES[i]
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _set_txstyles(prs, title_pt: float, body_pt) -> None:
|
|
81
|
+
"""重设母版文字样式的字号,使其符合 16:9 课件的可读性。"""
|
|
82
|
+
txStyles = prs.slide_master._element.find(qn("p:txStyles"))
|
|
83
|
+
if txStyles is None:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
def set_levels(style_tag: str, sizes) -> None:
|
|
87
|
+
node = txStyles.find(qn(style_tag))
|
|
88
|
+
if node is None:
|
|
89
|
+
return
|
|
90
|
+
for i, size in enumerate(sizes, start=1):
|
|
91
|
+
lvl = node.find(qn(f"a:lvl{i}pPr"))
|
|
92
|
+
if lvl is None:
|
|
93
|
+
continue
|
|
94
|
+
d = lvl.find(qn("a:defRPr"))
|
|
95
|
+
if d is None:
|
|
96
|
+
d = lvl.makeelement(qn("a:defRPr"), {})
|
|
97
|
+
lvl.append(d)
|
|
98
|
+
d.set("sz", str(int(size * 100)))
|
|
99
|
+
|
|
100
|
+
set_levels("p:titleStyle", [title_pt])
|
|
101
|
+
set_levels("p:bodyStyle", list(body_pt))
|
|
102
|
+
set_levels("p:otherStyle", [title_pt if False else body_pt[0]])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _rescale_horizontally(prs, factor: float) -> None:
|
|
106
|
+
"""把母版与所有版式里形状的 x / cx 按比例缩放(保持 y / cy 不变)。"""
|
|
107
|
+
from pptx.oxml.ns import qn as _qn
|
|
108
|
+
|
|
109
|
+
roots = [prs.slide_master._element]
|
|
110
|
+
roots += [lay._element for lay in prs.slide_layouts]
|
|
111
|
+
for root in roots:
|
|
112
|
+
for xfrm in root.iter(_qn("a:xfrm")):
|
|
113
|
+
off = xfrm.find(_qn("a:off"))
|
|
114
|
+
ext = xfrm.find(_qn("a:ext"))
|
|
115
|
+
if off is not None and off.get("x"):
|
|
116
|
+
off.set("x", str(int(int(off.get("x")) * factor)))
|
|
117
|
+
if ext is not None and ext.get("cx"):
|
|
118
|
+
ext.set("cx", str(int(int(ext.get("cx")) * factor)))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# --------------------------------------------------------------------------
|
|
122
|
+
def _theme_root(prs):
|
|
123
|
+
"""python-pptx 把 theme 当作不透明 Part,需要用 blob 读写。"""
|
|
124
|
+
from lxml import etree
|
|
125
|
+
from pptx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
126
|
+
|
|
127
|
+
part = prs.slide_master.part.part_related_by(RT.THEME)
|
|
128
|
+
root = getattr(part, "_element", None)
|
|
129
|
+
if root is None:
|
|
130
|
+
root = etree.fromstring(part.blob)
|
|
131
|
+
return part, root
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _save_theme(part, root) -> None:
|
|
135
|
+
from lxml import etree
|
|
136
|
+
|
|
137
|
+
part._blob = etree.tostring(root, xml_declaration=True, encoding="UTF-8", standalone=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _patch_theme(prs, pal: Dict[str, str], font: str, theme_name: str) -> None:
|
|
141
|
+
part, root = _theme_root(prs)
|
|
142
|
+
clrScheme = root.find(f".//{qn('a:clrScheme')}")
|
|
143
|
+
if clrScheme is not None:
|
|
144
|
+
clrScheme.set("name", theme_name)
|
|
145
|
+
for child in list(clrScheme):
|
|
146
|
+
key = child.tag.split("}")[-1]
|
|
147
|
+
color = pal.get(key)
|
|
148
|
+
if not color:
|
|
149
|
+
continue
|
|
150
|
+
for sub in list(child):
|
|
151
|
+
child.remove(sub)
|
|
152
|
+
srgb = child.makeelement(qn("a:srgbClr"), {"val": color})
|
|
153
|
+
child.append(srgb)
|
|
154
|
+
|
|
155
|
+
fontScheme = root.find(f".//{qn('a:fontScheme')}")
|
|
156
|
+
if fontScheme is not None:
|
|
157
|
+
fontScheme.set("name", theme_name)
|
|
158
|
+
for which in ("majorFont", "minorFont"):
|
|
159
|
+
node = fontScheme.find(qn(f"a:{which}"))
|
|
160
|
+
if node is None:
|
|
161
|
+
continue
|
|
162
|
+
for tag in ("a:latin", "a:ea", "a:cs"):
|
|
163
|
+
el = node.find(qn(tag))
|
|
164
|
+
if el is None:
|
|
165
|
+
el = node.makeelement(qn(tag), {})
|
|
166
|
+
node.append(el)
|
|
167
|
+
el.set("typeface", font)
|
|
168
|
+
|
|
169
|
+
_save_theme(part, root)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _patch_master(prs, pal: Dict[str, str]) -> None:
|
|
173
|
+
master = prs.slide_master
|
|
174
|
+
W, H = prs.slide_width, prs.slide_height
|
|
175
|
+
|
|
176
|
+
# 纯白背景,保证任何版式下文字都清晰
|
|
177
|
+
cSld = master._element.find(qn("p:cSld"))
|
|
178
|
+
if cSld is not None:
|
|
179
|
+
old = cSld.find(qn("p:bg"))
|
|
180
|
+
if old is not None:
|
|
181
|
+
cSld.remove(old)
|
|
182
|
+
bg = cSld.makeelement(qn("p:bg"), {})
|
|
183
|
+
bgPr = bg.makeelement(qn("p:bgPr"), {})
|
|
184
|
+
solid = bgPr.makeelement(qn("a:solidFill"), {})
|
|
185
|
+
solid.append(solid.makeelement(qn("a:srgbClr"), {"val": "FFFFFF"}))
|
|
186
|
+
bgPr.append(solid)
|
|
187
|
+
bgPr.append(bgPr.makeelement(qn("a:effectLst"), {}))
|
|
188
|
+
bg.append(bgPr)
|
|
189
|
+
cSld.insert(0, bg)
|
|
190
|
+
|
|
191
|
+
spTree = master.shapes._spTree
|
|
192
|
+
accent = pal.get("accent1", "2F6FED")
|
|
193
|
+
accent2 = pal.get("accent2", "F08C00")
|
|
194
|
+
|
|
195
|
+
# 底部细色条(母版装饰,所有版式/幻灯片自动继承)
|
|
196
|
+
bar_h = max(int(H * 0.012), 9144)
|
|
197
|
+
spTree.append(_rect_sp(0, int(H) - bar_h, int(W * 0.62), bar_h, accent, 900, "__builtin_bar1"))
|
|
198
|
+
spTree.append(_rect_sp(int(W * 0.62), int(H) - bar_h, int(W * 0.38), bar_h, accent2, 901, "__builtin_bar2"))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _rect_sp(x: int, y: int, cx: int, cy: int, color: str, shape_id: int, name: str):
|
|
202
|
+
"""构造一个无描边的纯色矩形 <p:sp>(用于母版装饰)。"""
|
|
203
|
+
from pptx.oxml import parse_xml
|
|
204
|
+
from pptx.oxml.ns import nsdecls
|
|
205
|
+
|
|
206
|
+
xml = (
|
|
207
|
+
f'<p:sp {nsdecls("p", "a")}>'
|
|
208
|
+
f'<p:nvSpPr><p:cNvPr id="{shape_id}" name="{name}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>'
|
|
209
|
+
f'<p:spPr>'
|
|
210
|
+
f'<a:xfrm><a:off x="{int(x)}" y="{int(y)}"/><a:ext cx="{int(cx)}" cy="{int(cy)}"/></a:xfrm>'
|
|
211
|
+
f'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>'
|
|
212
|
+
f'<a:solidFill><a:srgbClr val="{parse_color(color)}"/></a:solidFill>'
|
|
213
|
+
f'<a:ln><a:noFill/></a:ln>'
|
|
214
|
+
f'</p:spPr>'
|
|
215
|
+
f'<p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody>'
|
|
216
|
+
f'</p:sp>'
|
|
217
|
+
)
|
|
218
|
+
return parse_xml(xml)
|