dsh-ppt-studio 1.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.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +480 -0
  3. package/agent-presets/ppt/agent.cordis.yml +308 -0
  4. package/agent-presets/ppt/preset.yml +3 -0
  5. package/cordis.patch.yml +29 -0
  6. package/docs/00-/350/256/276/350/256/241/346/272/220.md +537 -0
  7. package/docs/01-/351/234/200/346/261/202/344/270/216/347/233/256/346/240/207.md +136 -0
  8. package/docs/02-/346/212/200/346/234/257/346/212/245/345/221/212.md +483 -0
  9. package/docs/03-/346/233/264/346/226/260/346/227/245/345/277/227.md +2898 -0
  10. package/docs/04-/350/267/257/347/272/277/345/233/276/344/270/216/351/207/214/347/250/213/347/242/221.md +113 -0
  11. package/docs/05-/350/277/255/344/273/243/346/265/201/347/250/213.md +210 -0
  12. package/docs/06-/350/257/204/345/256/241/344/270/216/346/265/213/350/257/225.md +296 -0
  13. package/docs/fidelity-report.md +225 -0
  14. package/examples/fx/fx-mini/deck.yaml +39 -0
  15. package/examples/fx/fx-mini/pages/01.yaml +6 -0
  16. package/examples/fx/fx-mini/pages/02.yaml +2 -0
  17. package/examples/fx/fx-mini/pages/03.yaml +6 -0
  18. package/examples/fx/fx-pro/deck.yaml +48 -0
  19. package/examples/fx/fx-pro/pages/01_cover.yaml +40 -0
  20. package/examples/fx/fx-pro/pages/02_arch.yaml +55 -0
  21. package/examples/fx/fx-pro/pages/03_table.yaml +20 -0
  22. package/examples/fx/fx-pro/pages/04_chart.yaml +40 -0
  23. package/examples/fx/fx-pro/pages/05_timeline.yaml +71 -0
  24. package/examples/fx/fx-pro/pages/06_longtext.yaml +13 -0
  25. package/examples/fx/fx-pro/pages/07_safearea.yaml +33 -0
  26. package/examples/fx/fx-pro/pages/08_contrast.yaml +22 -0
  27. package/examples/fx/fx-pro/pages/09_custgeom.yaml +32 -0
  28. package/examples/fx/fx-pro/pages/10_numbers.yaml +25 -0
  29. package/examples/fx/fx-pro/pages/11_section.yaml +20 -0
  30. package/examples/fx/fx-pro/pages/12_summary.yaml +57 -0
  31. package/examples/fx/fx-pro/preview/01_pages_01_cover.html +1 -0
  32. package/examples/fx/fx-pro/preview/02_pages_02_arch.html +3 -0
  33. package/examples/fx/fx-pro/preview/03_pages_03_table.html +1 -0
  34. package/examples/fx/fx-pro/preview/04_pages_04_chart.html +3 -0
  35. package/examples/fx/fx-pro/preview/05_pages_05_timeline.html +2 -0
  36. package/examples/fx/fx-pro/preview/06_pages_06_longtext.html +4 -0
  37. package/examples/fx/fx-pro/preview/07_pages_07_safearea.html +2 -0
  38. package/examples/fx/fx-pro/preview/08_pages_08_contrast.html +1 -0
  39. package/examples/fx/fx-pro/preview/09_pages_09_custgeom.html +1 -0
  40. package/examples/fx/fx-pro/preview/10_pages_10_numbers.html +1 -0
  41. package/examples/fx/fx-pro/preview/11_pages_11_section.html +1 -0
  42. package/examples/fx/fx-pro/preview/12_pages_12_summary.html +1 -0
  43. package/examples/fx/fx-pro/preview/deck.html +10 -0
  44. package/examples/fx/fx-pro/preview/layout.json +2325 -0
  45. package/examples/fx/fx-pro/preview/measured.json +814 -0
  46. package/examples/fx/fx-pro.pptx +0 -0
  47. package/examples/fx/ren-single/01.png +0 -0
  48. package/examples/fx/ren-spliced/01.png +0 -0
  49. package/examples/fx/ren-spliced/03.png +0 -0
  50. package/examples/fx/seed.pptx +0 -0
  51. package/examples/fx/single.pptx +0 -0
  52. package/examples/fx/spliced.pptx +0 -0
  53. package/examples/smoke/deck.yaml +26 -0
  54. package/examples/smoke/imported/deck.yaml +30 -0
  55. package/examples/smoke/imported/import-styles.json +126 -0
  56. package/examples/smoke/imported/pages/slide_01.yaml +31 -0
  57. package/examples/smoke/imported/pages/slide_02.yaml +38 -0
  58. package/examples/smoke/imported/pages/slide_03.yaml +55 -0
  59. package/examples/smoke/imported/preview-rt/01_pages_slide_01.html +1 -0
  60. package/examples/smoke/imported/preview-rt/02_pages_slide_02.html +1 -0
  61. package/examples/smoke/imported/preview-rt/03_pages_slide_03.html +1 -0
  62. package/examples/smoke/imported/preview-rt/deck.html +1 -0
  63. package/examples/smoke/imported/preview-rt/layout.json +434 -0
  64. package/examples/smoke/imported/reference/previews/01.png +0 -0
  65. package/examples/smoke/imported/reference/previews/02.png +0 -0
  66. package/examples/smoke/imported/reference/previews/03.png +0 -0
  67. package/examples/smoke/imported/source.pptx +0 -0
  68. package/examples/smoke/media-test.png +0 -0
  69. package/examples/smoke/out-smoke.pptx +0 -0
  70. package/examples/smoke/pages/01_cover.yaml +27 -0
  71. package/examples/smoke/pages/02_content.yaml +42 -0
  72. package/examples/smoke/pages/03_chart.yaml +35 -0
  73. package/examples/smoke/preview/01_pages_01_cover.html +1 -0
  74. package/examples/smoke/preview/02_pages_02_content.html +1 -0
  75. package/examples/smoke/preview/03_pages_03_chart.html +1 -0
  76. package/examples/smoke/preview/deck.html +1 -0
  77. package/examples/smoke/preview/layout.json +378 -0
  78. package/lib/autodeclare.js +40 -0
  79. package/lib/commands.js +161 -0
  80. package/lib/crosscheck.js +300 -0
  81. package/lib/imgmeta.js +55 -0
  82. package/lib/index.js +239 -0
  83. package/lib/measurement.js +209 -0
  84. package/lib/msrender.js +123 -0
  85. package/lib/pptd/export-pptx.js +686 -0
  86. package/lib/pptd/import-pptx.js +774 -0
  87. package/lib/pptd/layout.js +209 -0
  88. package/lib/pptd/render-html.js +254 -0
  89. package/lib/pptd/schema.js +518 -0
  90. package/lib/pptd/svgCharts.js +147 -0
  91. package/lib/pptxPy.js +205 -0
  92. package/lib/preset-delivery.js +195 -0
  93. package/lib/preview-server.js +164 -0
  94. package/lib/router.js +165 -0
  95. package/lib/scaffold.js +315 -0
  96. package/lib/skill.js +174 -0
  97. package/lib/splice.js +219 -0
  98. package/lib/state.js +88 -0
  99. package/lib/surgical.js +369 -0
  100. package/lib/templates.js +400 -0
  101. package/lib/tools.js +955 -0
  102. package/lib/verify.js +828 -0
  103. package/lib/xmljs.js +112 -0
  104. package/lib/zips.js +117 -0
  105. package/package.json +81 -0
  106. package/scripts/audit-manual-facts.mjs +294 -0
  107. package/scripts/audit-template-fidelity.mjs +162 -0
  108. package/scripts/bench-m2.mjs +48 -0
  109. package/scripts/build-templates.mjs +63 -0
  110. package/scripts/build.mjs +14 -0
  111. package/scripts/build.sh +7 -0
  112. package/scripts/check-lib-freshness.mjs +70 -0
  113. package/scripts/check-preset.mjs +189 -0
  114. package/scripts/check-tracked-lf.mjs +84 -0
  115. package/scripts/dbg-import.mjs +13 -0
  116. package/scripts/dbg-media.mjs +10 -0
  117. package/scripts/dbg-tpl-inventory.mjs +23 -0
  118. package/scripts/dump-workflow-baseline.mjs +38 -0
  119. package/scripts/e2e-1.0.mjs +129 -0
  120. package/scripts/eval-skills-blind.mjs +91 -0
  121. package/scripts/eval-skills-score.mjs +129 -0
  122. package/scripts/export-orig-templates.ps1 +17 -0
  123. package/scripts/fixtures/workflow-baseline.json +300 -0
  124. package/scripts/fixtures.mjs +501 -0
  125. package/scripts/import-user-templates.mjs +60 -0
  126. package/scripts/install-desktop.mjs +312 -0
  127. package/scripts/install.mjs +209 -0
  128. package/scripts/migrate-templates-dualtrack.mjs +71 -0
  129. package/scripts/preflight-1.0.mjs +198 -0
  130. package/scripts/probe-asset-freshness.mjs +57 -0
  131. package/scripts/probe-effect-semantics.mjs +69 -0
  132. package/scripts/probe-reparse-safety.mjs +32 -0
  133. package/scripts/rebuild-templates-c11.mjs +69 -0
  134. package/scripts/regression-real.mjs +84 -0
  135. package/scripts/release-sync.mjs +425 -0
  136. package/scripts/shot-templates.mjs +49 -0
  137. package/scripts/smoke.mjs +2512 -0
  138. package/scripts/verify-bundle-install.mjs +281 -0
  139. package/scripts/verify-fresh-install.mjs +260 -0
  140. package/skills/ppt-studio-copy/SKILL.md +139 -0
  141. package/skills/ppt-studio-craft/SKILL.md +171 -0
  142. package/skills/ppt-studio-data/SKILL.md +173 -0
  143. package/skills/ppt-studio-manual/SKILL.md +104 -0
  144. package/templates/academic-white/deck.yaml +24 -0
  145. package/templates/academic-white/pages/_chart.yaml +43 -0
  146. package/templates/academic-white/pages/_compare.yaml +37 -0
  147. package/templates/academic-white/pages/_content.yaml +35 -0
  148. package/templates/academic-white/pages/_cover.yaml +28 -0
  149. package/templates/academic-white/pages/_ending.yaml +28 -0
  150. package/templates/academic-white/pages/_flow.yaml +61 -0
  151. package/templates/academic-white/preview.png +0 -0
  152. package/templates/academic-white/template.yaml +14 -0
  153. package/templates/business-blue/deck.yaml +28 -0
  154. package/templates/business-blue/pages/_chart.yaml +36 -0
  155. package/templates/business-blue/pages/_compare.yaml +45 -0
  156. package/templates/business-blue/pages/_content.yaml +80 -0
  157. package/templates/business-blue/pages/_cover.yaml +39 -0
  158. package/templates/business-blue/pages/_ending.yaml +32 -0
  159. package/templates/business-blue/pages/_flow.yaml +87 -0
  160. package/templates/business-blue/preview.png +0 -0
  161. package/templates/business-blue/template.yaml +14 -0
  162. package/templates/pitch-bold/deck.yaml +23 -0
  163. package/templates/pitch-bold/pages/_chart.yaml +32 -0
  164. package/templates/pitch-bold/pages/_compare.yaml +36 -0
  165. package/templates/pitch-bold/pages/_content.yaml +58 -0
  166. package/templates/pitch-bold/pages/_cover.yaml +31 -0
  167. package/templates/pitch-bold/pages/_ending.yaml +27 -0
  168. package/templates/pitch-bold/pages/_flow.yaml +46 -0
  169. package/templates/pitch-bold/preview.png +0 -0
  170. package/templates/pitch-bold/template.yaml +14 -0
  171. package/templates/tech-dark/deck.yaml +24 -0
  172. package/templates/tech-dark/pages/_chart.yaml +27 -0
  173. package/templates/tech-dark/pages/_compare.yaml +38 -0
  174. package/templates/tech-dark/pages/_content.yaml +51 -0
  175. package/templates/tech-dark/pages/_cover.yaml +30 -0
  176. package/templates/tech-dark/pages/_ending.yaml +25 -0
  177. package/templates/tech-dark/pages/_flow.yaml +80 -0
  178. package/templates/tech-dark/preview.png +0 -0
  179. package/templates/tech-dark/template.yaml +14 -0
@@ -0,0 +1,501 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 1.0.0 自包含测试夹具生成器(用户已清理历史工作区——此后测试不求外部资产):
4
+ * 生成 examples/fx/:
5
+ * fx-pro —— 高难度综合 12 页(声明制/安全区/对比度/渐变/alpha/custGeom/嵌套卡/表格/图表/跨页数字/来源标注)
6
+ * fx-mini —— 3 页迷你(单元素/空白页/极简)——快速路径
7
+ * 再导出 fx-pro 为 seed.pptx(splice/slice 的自产 source)。
8
+ */
9
+ import { rm, mkdir, writeFile } from 'node:fs/promises'
10
+ import { join, dirname } from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+ import { exportPptx } from '../lib/pptd/export-pptx.js'
13
+ import { resolveDeck } from '../lib/pptd/schema.js'
14
+
15
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
16
+ const fx = join(root, 'examples', 'fx')
17
+
18
+ const THEME = `theme:
19
+ colors:
20
+ ink: "#1F2937" # 主墨
21
+ primary: "#2563EB" # 主蓝
22
+ accent: "#F59E0B" # 强调橙
23
+ danger: "#C0392B" # 警示红
24
+ steel: "#64748B" # 辅助灰蓝
25
+ bg: "#F8FAFC" # 页面底
26
+ deep: "#0F172A" # 深底(白字承载)
27
+ # 扩展档(夹具调色:蓝/紫/绿阶 + 浅底)
28
+ blue-soft: "#DBEAFE"
29
+ blue-deep: "#1E3A8A"
30
+ blue-mid: "#3B82F6"
31
+ slate-deep: "#334155"
32
+ slate-soft: "#CBD5E1"
33
+ slate-lite: "#E2E8F0"
34
+ amber-soft: "#FEF3C7"
35
+ amber-deep: "#92400E"
36
+ amber-dark: "#78350F"
37
+ violet: "#8B5CF6"
38
+ green: "#10B981"
39
+ green-soft: "#D1FAE5"
40
+ green-deep: "#065F46"
41
+ textStyles:
42
+ title: {fontSize: 32, color: "$ink", bold: true, fontFamily: "微软雅黑"}
43
+ h1: {fontSize: 22, color: "$ink", bold: true, fontFamily: "微软雅黑"}
44
+ h2: {fontSize: 16, color: "$ink", bold: true, fontFamily: "微软雅黑"}
45
+ body: {fontSize: 14, color: "$ink", fontFamily: "微软雅黑"}
46
+ small: {fontSize: 12, color: "$steel", fontFamily: "微软雅黑"}
47
+ safeArea: {top: 34, bottom: 26, left: 40, right: 40}
48
+ minFontSize: 12
49
+ `
50
+
51
+ const P = (name, body) => [name, body].join('')
52
+
53
+ const pages = {
54
+ 'pages/01_cover.yaml': P('pageType: cover\nbackground: "#F8FAFC"\nexpectedOutOfSafeArea: [band]\n', `elements:
55
+ - elementId: band
56
+ elementType: shape
57
+ kind: rect
58
+ bounds: [0, 0, 960, 540]
59
+ fill: {type: gradient, stops: [{pos: 0, color: "#0F172A"}, {pos: 100, color: "#2563EB"}], angle: 90}
60
+ - elementId: glow
61
+ elementType: shape
62
+ kind: ellipse
63
+ bounds: [660, 34, 260, 260]
64
+ fill: {color: "#FFFFFF", alpha: 18}
65
+ - elementId: title
66
+ elementType: text
67
+ bounds: [80, 150, 700, 90]
68
+ content: {text: "能力验证演示(自建夹具)", style: "$title", color: "#FFFFFF"}
69
+ - elementId: sub
70
+ elementType: text
71
+ bounds: [80, 260, 600, 50]
72
+ content: {text: "PPTD 中间层 · 声明制 · 双轨审阅", style: "$h1", color: "#CBD5E1"}
73
+ - elementId: badge
74
+ elementType: shape
75
+ kind: chevron
76
+ bounds: [80, 420, 200, 56]
77
+ fill: "#F59E0B"
78
+ - elementId: badge_t
79
+ elementType: text
80
+ bounds: [100, 430, 160, 40]
81
+ content: {text: "v1.0.0", style: "$h2", color: "#0F172A", align: center}
82
+ expectedOverlaps:
83
+ - {pair: [band, glow]}
84
+ - {pair: [band, title]}
85
+ - {pair: [band, sub]}
86
+ - {pair: [band, badge]}
87
+ - {pair: [band, badge_t]}
88
+ - {pair: [glow, title]}
89
+ - {pair: [glow, sub]}
90
+ - {pair: [badge, badge_t]}
91
+ `),
92
+ 'pages/02_arch.yaml': P('pageType: content\nbackground: "#F8FAFC"\nsource: "夹具自造架构示意(三层嵌套 + 声明闭包)"\n', `elements:
93
+ - elementId: panel
94
+ elementType: shape
95
+ kind: roundRect
96
+ bounds: [60, 80, 840, 380]
97
+ fill: "#FFFFFF"
98
+ line: {color: "#CBD5E1", width: 1}
99
+ - elementId: box_a
100
+ elementType: shape
101
+ kind: rect
102
+ bounds: [100, 130, 350, 240]
103
+ fill: "#DBEAFE"
104
+ line: {color: "#2563EB", width: 1}
105
+ - elementId: box_a_t
106
+ elementType: text
107
+ bounds: [120, 150, 310, 60]
108
+ content: {text: "领域本体(知识层)", style: "$h2", color: "#1E3A8A"}
109
+ - elementId: box_a_b
110
+ elementType: text
111
+ bounds: [120, 230, 310, 120]
112
+ content: {text: "对象·属性·关系·约束\\n换场景 = 换实例,零重构", style: "$body", color: "#334155"}
113
+ - elementId: box_b
114
+ elementType: shape
115
+ kind: rect
116
+ bounds: [510, 130, 350, 240]
117
+ fill: "#FEF3C7"
118
+ line: {color: "#F59E0B", width: 1}
119
+ - elementId: box_b_t
120
+ elementType: text
121
+ bounds: [530, 150, 310, 60]
122
+ content: {text: "仿真内核(执行层)", style: "$h2", color: "#92400E"}
123
+ - elementId: box_b_b
124
+ elementType: text
125
+ bounds: [530, 230, 310, 120]
126
+ content: {text: "车间生产·负载评估·计划跟踪\\n结论可迁移(泛化性)", style: "$body", color: "#78350F"}
127
+ - elementId: arrow
128
+ elementType: line
129
+ points: [[450, 250], [508, 250]]
130
+ arrow: true
131
+ line: {color: "#2563EB", width: 2}
132
+ - elementId: foot
133
+ elementType: text
134
+ bounds: [100, 470, 760, 40]
135
+ content: {text: "知识层 → 映射 → 执行层:本体驱动仿真的泛化范式(A8 场景也在本页)", style: "$small"}
136
+ expectedOverlaps:
137
+ - {pair: [panel, box_a]}
138
+ - {pair: [panel, box_b]}
139
+ - {pair: [box_a, box_a_t]}
140
+ - {pair: [box_a, box_a_b]}
141
+ - {pair: [box_b, box_b_t]}
142
+ - {pair: [box_b, box_b_b]}
143
+ # 声明闭包:panel×box_a_t 等隔层对由包含关系传递自动通过,无需声明
144
+ `),
145
+ 'pages/03_table.yaml': P('pageType: content\nbackground: "#F8FAFC"\nsource: "夹具自造表(数据为演示样本)"\n', `elements:
146
+ - elementId: t_head
147
+ elementType: text
148
+ bounds: [60, 60, 400, 40]
149
+ content: {text: "对策-指标对照表", style: "$h1"}
150
+ - elementId: tbl
151
+ elementType: table
152
+ bounds: [60, 120, 840, 300]
153
+ cols: [对策, 指标A, 指标B, 说明]
154
+ rows:
155
+ - [线表决策辅助, 决策响应-32%, 准确率+12%, 基线 45.6%]
156
+ - [排程优化, 交付周期-18%, 利用率+9%, 中型场景]
157
+ - [生产流程仿真, 节拍-15%, 一次合格率+8%, 泛化验证]
158
+ - elementId: t_note
159
+ elementType: text
160
+ bounds: [60, 450, 840, 40]
161
+ content: {text: "注:数据为演示样本(45.6% 为跨页对账演示数字)", style: "$small"}
162
+ `),
163
+ 'pages/04_chart.yaml': P('pageType: content\nbackground: "#F8FAFC"\nsource: "夹具自造折线(演示)"\n', `elements:
164
+ - elementId: c_head
165
+ elementType: text
166
+ bounds: [60, 60, 400, 40]
167
+ content: {text: "仿真运行趋势", style: "$h1"}
168
+ - elementId: chart
169
+ elementType: chart
170
+ bounds: [60, 130, 480, 300]
171
+ chart:
172
+ type: line
173
+ data:
174
+ cols: [month, pass]
175
+ rows:
176
+ - ["1月", 92]
177
+ - ["2月", 94]
178
+ - ["3月", 95]
179
+ - ["4月", 96]
180
+ series:
181
+ - {name: 一次合格率, x: month, y: pass}
182
+ colors: ["$primary"]
183
+ - elementId: key
184
+ elementType: shape
185
+ kind: roundRect
186
+ bounds: [580, 130, 320, 300]
187
+ fill: "#FFFFFF"
188
+ line: {color: "#CBD5E1", width: 1}
189
+ - elementId: key_t
190
+ elementType: text
191
+ bounds: [610, 160, 260, 60]
192
+ content: {text: "关键指标", style: "$h2"}
193
+ - elementId: key_body
194
+ elementType: text
195
+ bounds: [610, 240, 260, 160]
196
+ content: {text: "一次合格率 96%(+8pt)\\n综合评 45.6% 提升\\n节拍 −15%", style: "$body"}
197
+ expectedOverlaps:
198
+ - {pair: [key, key_t]}
199
+ - {pair: [key, key_body]}
200
+ `),
201
+ 'pages/05_timeline.yaml': P('pageType: content\nbackground: "#F8FAFC"\n', `elements:
202
+ - elementId: tl_head
203
+ elementType: text
204
+ bounds: [60, 60, 500, 40]
205
+ content: {text: "实施路线(五阶段)", style: "$h1"}
206
+ - elementId: tl_line
207
+ elementType: line
208
+ points: [[80, 200], [860, 200]]
209
+ line: {color: "#94A3B8", width: 2}
210
+ - elementId: st1
211
+ elementType: shape
212
+ kind: pentagon
213
+ bounds: [60, 140, 140, 60]
214
+ fill: "#2563EB"
215
+ - elementId: st1_t
216
+ elementType: text
217
+ bounds: [70, 148, 120, 44]
218
+ content: {text: "建模", style: "$h2", color: "#FFFFFF", align: center}
219
+ - elementId: st2
220
+ elementType: shape
221
+ kind: pentagon
222
+ bounds: [220, 140, 140, 60]
223
+ fill: "#3B82F6"
224
+ - elementId: st2_t
225
+ elementType: text
226
+ bounds: [230, 148, 120, 44]
227
+ content: {text: "映射", style: "$h2", color: "#FFFFFF", align: center}
228
+ - elementId: st3
229
+ elementType: shape
230
+ kind: pentagon
231
+ bounds: [380, 140, 140, 60]
232
+ fill: "#F59E0B"
233
+ - elementId: st3_t
234
+ elementType: text
235
+ bounds: [390, 148, 120, 44]
236
+ content: {text: "仿真", style: "$h2", color: "#FFFFFF", align: center}
237
+ - elementId: st4
238
+ elementType: shape
239
+ kind: pentagon
240
+ bounds: [540, 140, 140, 60]
241
+ fill: "#8B5CF6"
242
+ - elementId: st4_t
243
+ elementType: text
244
+ bounds: [550, 148, 120, 44]
245
+ content: {text: "验证", style: "$h2", color: "#FFFFFF", align: center}
246
+ - elementId: st5
247
+ elementType: shape
248
+ kind: pentagon
249
+ bounds: [700, 140, 140, 60]
250
+ fill: "#10B981"
251
+ - elementId: st5_t
252
+ elementType: text
253
+ bounds: [710, 148, 120, 44]
254
+ content: {text: "推广", style: "$h2", color: "#FFFFFF", align: center}
255
+ - elementId: tl_note
256
+ elementType: text
257
+ bounds: [80, 300, 800, 100]
258
+ content: {text: "单向推进 + 每阶段门禁:建模评审 → 映射校验 → 仿真对标 → 验证报告 → 批量复制\\n(时间线为示意图,非真实排期)", style: "$body"}
259
+ expectedOverlaps:
260
+ - {pair: [st1, tl_line]}
261
+ - {pair: [st2, tl_line]}
262
+ - {pair: [st3, tl_line]}
263
+ - {pair: [st4, tl_line]}
264
+ - {pair: [st5, tl_line]}
265
+ - {pair: [st1, st1_t]}
266
+ - {pair: [st2, st2_t]}
267
+ - {pair: [st3, st3_t]}
268
+ - {pair: [st4, st4_t]}
269
+ - {pair: [st5, st5_t]}
270
+ `),
271
+ 'pages/06_longtext.yaml': P('pageType: content\nbackground: "#F8FAFC"\n', `elements:
272
+ - elementId: lt_head
273
+ elementType: text
274
+ bounds: [60, 60, 500, 40]
275
+ content: {text: "长文本容纳(保守估算 + 显式断行)", style: "$h1"}
276
+ - elementId: lt_body
277
+ elementType: text
278
+ bounds: [60, 130, 840, 340]
279
+ content:
280
+ text: "这一段用于验证长文本在保守估算下的处理:中文全角加粗按 1.06 系数、行高 1.2、标点禁则——\\n显式断行是长句的最佳实践,容器也必须给足高度。\\n本夹具故意把每行控制在 40 字以内,并预留 340px 高度(约 20 行×16.8px),确保门禁零错误,\\n同时验证保守估算不误报:若容器足够,estimate 与实测应一致。"
281
+ style: "$body"
282
+ `),
283
+ 'pages/07_safearea.yaml': P('pageType: content\nbackground: "#F8FAFC"\nexpectedOutOfSafeArea: [logo, logo_t, footer, footer_t]\n', `elements:
284
+ - elementId: logo
285
+ elementType: shape
286
+ kind: ellipse
287
+ bounds: [895, 8, 40, 30]
288
+ fill: "#2563EB"
289
+ - elementId: logo_t
290
+ elementType: text
291
+ bounds: [897, 12, 36, 24]
292
+ content: {text: "S", style: "$body", color: "#FFFFFF", align: center}
293
+ - elementId: sa_title
294
+ elementType: text
295
+ bounds: [60, 70, 600, 40]
296
+ content: {text: "安全区与页脚带声明(logo 落在安全区外)", style: "$h1"}
297
+ - elementId: sa_body
298
+ elementType: text
299
+ bounds: [60, 140, 840, 200]
300
+ content: {text: "主题 safeArea 定义为顶/底/左/右 34/26/40/40px:内容不得侵入。\\n页脚带(y≥514)属于安全区外——如需放角标,用 expectedOutOfSafeArea 声明(已声明 logo)。", style: "$body"}
301
+ - elementId: footer
302
+ elementType: shape
303
+ kind: rect
304
+ bounds: [0, 514, 960, 26]
305
+ fill: "#E2E8F0"
306
+ - elementId: footer_t
307
+ elementType: text
308
+ bounds: [60, 518, 300, 20]
309
+ content: {text: "页脚带:夹具页脚 © v1.0.0", style: "$small"}
310
+ expectedOverlaps:
311
+ - {pair: [logo, logo_t]}
312
+ - {pair: [footer, footer_t]}
313
+ `),
314
+ 'pages/08_contrast.yaml': P('pageType: content\nbackground: "#F8FAFC"\n', `elements:
315
+ - elementId: ct_head
316
+ elementType: text
317
+ bounds: [60, 60, 600, 40]
318
+ content: {text: "对比度承载面(渐变深底白字)", style: "$h1"}
319
+ - elementId: carrier
320
+ elementType: shape
321
+ kind: parallelogram
322
+ bounds: [60, 150, 400, 180]
323
+ fill: {type: gradient, stops: [{pos: 0, color: "#0F172A"}, {pos: 100, color: "#2563EB"}], angle: 90}
324
+ - elementId: carrier_t
325
+ elementType: text
326
+ bounds: [110, 230, 300, 60]
327
+ content: {text: "深底上的白字", style: "$h2", color: "#FFFFFF", align: center}
328
+ - elementId: ct_note
329
+ elementType: text
330
+ bounds: [520, 170, 380, 140]
331
+ content: {text: '对比度启发式以最近承载面(渐变中途 stop)为基准——本页不应报"白字 vs 页面背景"误报。', style: "$body"}
332
+ expectedOverlaps:
333
+ - {pair: [carrier, carrier_t]}
334
+ `),
335
+ 'pages/09_custgeom.yaml': P('pageType: content\nbackground: "#F8FAFC"\nexpectedOutOfSafeArea: [arc]\n', `elements:
336
+ - elementId: cg_head
337
+ elementType: text
338
+ bounds: [60, 60, 600, 40]
339
+ content: {text: "custGeom 自定义几何(弧带装饰)", style: "$h1"}
340
+ - elementId: arc
341
+ elementType: shape
342
+ kind: custGeom
343
+ bounds: [600, 320, 320, 200]
344
+ path:
345
+ w: 320
346
+ h: 200
347
+ commands:
348
+ - {cmd: moveTo, pts: [[0, 200]]}
349
+ - {cmd: arcTo, pts: [[160, 100]], wR: 160, hR: 100, stAng: 0, swAng: 5400000}
350
+ - {cmd: lnTo, pts: [[200, 60]]}
351
+ - {cmd: cubicBezTo, pts: [[180, 40], [120, 20], [0, 20]]}
352
+ - {cmd: close}
353
+ fill: {color: "#2563EB", alpha: 35}
354
+ - elementId: cg_body
355
+ elementType: text
356
+ bounds: [60, 140, 500, 160]
357
+ content: {text: "弧带为装饰层(role: decoration 豁免重叠);几何:moveTo/arcTo/lnTo/cubicBezTo/close。", style: "$body"}
358
+ - elementId: deco
359
+ elementType: shape
360
+ kind: ellipse
361
+ bounds: [700, 60, 120, 120]
362
+ fill: {color: "#F59E0B", alpha: 30}
363
+ role: decoration
364
+ `),
365
+ 'pages/10_numbers.yaml': P('pageType: content\nbackground: "#F8FAFC"\nsource: "夹具自造摘要(45.6% 与 04 页跨页)"\n', `elements:
366
+ - elementId: num_card
367
+ elementType: shape
368
+ kind: roundRect
369
+ bounds: [60, 90, 380, 220]
370
+ fill: "#FFFFFF"
371
+ line: {color: "#CBD5E1", width: 1}
372
+ - elementId: num_v
373
+ elementType: text
374
+ bounds: [100, 120, 300, 80]
375
+ content: {text: "45.6%", style: "$title", color: "#2563EB", align: center}
376
+ - elementId: num_l
377
+ elementType: text
378
+ bounds: [100, 220, 300, 40]
379
+ content: {text: "综合效率提升(与 04 页同值)", style: "$small", align: center}
380
+ - elementId: num_note
381
+ elementType: text
382
+ bounds: [480, 120, 420, 160]
383
+ content: {text: "跨页演示:本页 45.6% 与第 04 页 key_body 的 45.6% 应出现在 ppt_crosscheck 材料包。", style: "$body"}
384
+ expectedOverlaps:
385
+ - {pair: [num_card, num_v]}
386
+ - {pair: [num_card, num_l]}
387
+ `),
388
+ 'pages/11_section.yaml': P('pageType: content\nbackground: "#F8FAFC"\nexpectedOutOfSafeArea: [sec_band]\n', `elements:
389
+ - elementId: sec_band
390
+ elementType: shape
391
+ kind: rect
392
+ bounds: [0, 0, 960, 540]
393
+ fill: "#0F172A"
394
+ - elementId: sec_num
395
+ elementType: text
396
+ bounds: [80, 180, 300, 120]
397
+ content: {text: "03", style: "$title", color: "#F59E0B"}
398
+ - elementId: sec_t
399
+ elementType: text
400
+ bounds: [400, 200, 480, 80]
401
+ content: {text: "验证与展望", style: "$title", color: "#FFFFFF"}
402
+ expectedOverlaps:
403
+ - {pair: [sec_band, sec_num]}
404
+ - {pair: [sec_band, sec_t]}
405
+ `),
406
+ 'pages/12_summary.yaml': P('pageType: content\nbackground: "#F8FAFC"\n', `elements:
407
+ - elementId: sm_head
408
+ elementType: text
409
+ bounds: [60, 60, 600, 40]
410
+ content: {text: "总结:一条可验证的生产线", style: "$h1"}
411
+ - elementId: sm_a
412
+ elementType: shape
413
+ kind: roundRect
414
+ bounds: [60, 130, 260, 180]
415
+ fill: "#DBEAFE"
416
+ - elementId: sm_a_t
417
+ elementType: text
418
+ bounds: [80, 150, 220, 60]
419
+ content: {text: "声明制", style: "$h2", color: "#1E3A8A", align: center}
420
+ - elementId: sm_a_b
421
+ elementType: text
422
+ bounds: [80, 220, 220, 70]
423
+ content: {text: "冲突防不住?声明设计意图", style: "$body"}
424
+ - elementId: sm_b
425
+ elementType: shape
426
+ kind: roundRect
427
+ bounds: [350, 130, 260, 180]
428
+ fill: "#FEF3C7"
429
+ - elementId: sm_b_t
430
+ elementType: text
431
+ bounds: [370, 150, 220, 60]
432
+ content: {text: "双轨审阅", style: "$h2", color: "#92400E", align: center}
433
+ - elementId: sm_b_b
434
+ elementType: text
435
+ bounds: [370, 220, 220, 70]
436
+ content: {text: "数字门禁 + 视觉审阅 + 真渲染", style: "$body"}
437
+ - elementId: sm_c
438
+ elementType: shape
439
+ kind: roundRect
440
+ bounds: [640, 130, 260, 180]
441
+ fill: "#D1FAE5"
442
+ - elementId: sm_c_t
443
+ elementType: text
444
+ bounds: [660, 150, 220, 60]
445
+ content: {text: "保真交付", style: "$h2", color: "#065F46", align: center}
446
+ - elementId: sm_c_b
447
+ elementType: text
448
+ bounds: [660, 220, 220, 70]
449
+ content: {text: "splice / slice / patch", style: "$body"}
450
+ - elementId: sm_foot
451
+ elementType: text
452
+ bounds: [60, 360, 840, 90]
453
+ content: {text: "结论:元素区块冲突被机器防住(声明制),质量可验证(三轨),保真可一指令(splice)。", style: "$body"}
454
+ expectedOverlaps:
455
+ - {pair: [sm_a, sm_a_t]}
456
+ - {pair: [sm_a, sm_a_b]}
457
+ - {pair: [sm_b, sm_b_t]}
458
+ - {pair: [sm_b, sm_b_b]}
459
+ - {pair: [sm_c, sm_c_t]}
460
+ - {pair: [sm_c, sm_c_b]}
461
+ `),
462
+ }
463
+
464
+ const miniPages = {
465
+ 'pages/01.yaml': 'pageType: content\nelements:\n - elementId: only\n elementType: text\n bounds: [60, 60, 400, 60]\n content: {text: "唯一元素页", style: "$body"}\n',
466
+ 'pages/02.yaml': 'pageType: content\n# 空页(仅背景)\n',
467
+ 'pages/03.yaml': 'pageType: content\nelements:\n - elementId: t\n elementType: text\n bounds: [40, 40, 300, 40]\n content: {text: "极简页", style: "$body"}\n',
468
+ }
469
+
470
+ export async function genFixtures() {
471
+ await rm(fx, { recursive: true, force: true })
472
+ await mkdir(join(fx, 'fx-pro', 'pages'), { recursive: true })
473
+ await mkdir(join(fx, 'fx-mini', 'pages'), { recursive: true })
474
+ await writeFile(join(fx, 'fx-pro', 'deck.yaml'), `# 高难度综合夹具(v1.0.0 自包含测试案例;声明齐备 → verify 应 0 错误)
475
+ version: 1
476
+ title: "能力验证演示"
477
+ size: [960, 540]
478
+ ${THEME}pages:
479
+ ${Object.keys(pages).map((k) => ` - ${k}`).join('\n')}
480
+ `)
481
+ for (const [k, body] of Object.entries(pages)) await writeFile(join(fx, 'fx-pro', k), body)
482
+ await writeFile(join(fx, 'fx-mini', 'deck.yaml'), `# 迷你夹具(快速路径)
483
+ version: 1
484
+ title: "迷你演示"
485
+ size: [960, 540]
486
+ ${THEME}pages:
487
+ - pages/01.yaml
488
+ - pages/02.yaml
489
+ - pages/03.yaml
490
+ `)
491
+ for (const [k, body] of Object.entries(miniPages)) await writeFile(join(fx, 'fx-mini', k), body)
492
+ // 自产 seed:fx-pro 导出(splice/slice 的自产 source——不依赖任何外部资产)
493
+ const ctx = await resolveDeck(join(fx, 'fx-pro'))
494
+ const r = await exportPptx(ctx, { out: join(fx, 'seed.pptx'), engine: 'pptd' })
495
+ return { fx, seed: r.file, pages: ctx.pages.length }
496
+ }
497
+
498
+ if (process.argv[1] && import.meta.url.replace(/\\/g, '/').endsWith(process.argv[1].replace(/\\/g, '/'))) {
499
+ const info = await genFixtures()
500
+ console.log('fixtures generated:', info.fx, '| seed:', info.seed, '| pages:', info.pages)
501
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 批量收纳用户模板(一次性/可复用):<目录>/*.pptx → ppt_import → registerTemplate → 清临时。
3
+ * 全部内容(页面/媒体/预览图)复制到模板库 templates/<id>/,完成后源目录可安全删除。
4
+ * 用法:node scripts/import-user-templates.mjs [目录](缺省桌面 ppt模板库)
5
+ */
6
+ import { join, dirname, basename } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { rm, mkdir, readdir } from 'node:fs/promises'
9
+ import { importPptx } from '../lib/pptd/import-pptx.js'
10
+ import { registerTemplate, listTemplates, TEMPLATES_DIR } from '../lib/templates.js'
11
+ import { resolveDeck } from '../lib/pptd/schema.js'
12
+ import { renderDeck } from '../lib/pptd/render-html.js'
13
+ import { verifyDeck } from '../lib/verify.js'
14
+
15
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
16
+ const src = process.argv[2] ?? 'C:/Users/11867/Desktop/ppt模板库'
17
+ const work = join(root, 'examples', 'tpl-import-work')
18
+
19
+ const files = (await readdir(src)).filter((f) => /\.pptx$/i.test(f))
20
+ if (!files.length) { console.log('无 .pptx'); process.exit(0) }
21
+
22
+ const results = []
23
+ for (const f of files) {
24
+ const pptx = join(src, f)
25
+ const ws = join(work, f.replace(/\.pptx$/i, ''))
26
+ console.log(`\n===== ${f} =====`)
27
+ try {
28
+ await rm(ws, { recursive: true, force: true })
29
+ await mkdir(ws, { recursive: true })
30
+ const imp = await importPptx(pptx, ws)
31
+ console.log(`import: ${imp.pages} 页 / ${imp.media.length} 媒体 / ${imp.warnings.join(';')}`)
32
+ const name = basename(f, '.pptx')
33
+ const style = /论文|答辩/.test(name) ? '学术答辩' : /总结|汇报/.test(name) ? '工作总结' : /商务/.test(name) ? '商务' : '用户模板'
34
+ const scene = /论文|答辩/.test(name) ? '毕业论文/项目答辩' : /总结|汇报/.test(name) ? '部门汇报/年终总结' : '商务演示'
35
+ const reg = await registerTemplate(ws, { id: slug(name), name, style, scene })
36
+ // 自验:模板工作区可校验 + 渲染(不强制 0 错误——外部导入模板为内容参照,可 autoDeclare 清理)
37
+ const ctx = await resolveDeck(reg.dir)
38
+ const r = await renderDeck(ctx, {})
39
+ const v = verifyDeck(r.layout)
40
+ const errs = v.text.split('\n').filter((l) => l.includes('[✗]')).length
41
+ results.push({ id: reg.id, name, pages: reg.pages, media: imp.media.length, preview: reg.preview ? '✓' : '—', errs })
42
+ console.log(`register: ${reg.id} → ${reg.dir}(${reg.pages} 页母版;verify 概要错误 ${errs} 条 [外部模板为内容参照,可 autoDeclare 清理])`)
43
+ } catch (e) {
44
+ results.push({ id: '-', name: f, error: e?.message })
45
+ console.log(`FAIL: ${e?.message}`)
46
+ } finally {
47
+ await rm(ws, { recursive: true, force: true })
48
+ }
49
+ }
50
+ await rm(work, { recursive: true, force: true })
51
+
52
+ console.log('\n===== 汇总 =====')
53
+ for (const r of results) console.log(`- ${r.id ?? 'FAIL'}|${r.name}|${r.pages ?? r.error ?? ''} ${r.preview ?? ''} ${r.errs !== undefined ? '|verify错误 ' + r.errs : ''}`)
54
+ const final = await listTemplates()
55
+ console.log(`\n模板库现有 ${final.length} 套:${final.map((t) => t.id).join(', ')}`)
56
+ console.log(`模板库目录:${TEMPLATES_DIR}(源目录 ${src} 现可删除)`)
57
+
58
+ function slug(s) {
59
+ return String(s).toLowerCase().replace(/[^\w\u4e00-\u9fff-]+/g, '-').replace(/^-+|-+$/g, '') || 'template'
60
+ }