@topmindspace/tms-skills 2.0.1 → 2.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 (95) hide show
  1. package/CHANGELOG.md +145 -134
  2. package/README.md +1 -1
  3. package/bin/tms-skills.js +160 -160
  4. package/package.json +48 -48
  5. package/top-ppt-html/README.md +254 -254
  6. package/top-ppt-html/SKILL.md +111 -111
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
  8. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
  9. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
  10. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
  11. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
  12. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
  13. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
  14. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
  15. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
  16. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
  17. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
  18. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
  19. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
  20. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
  21. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
  22. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
  23. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
  24. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
  25. package/top-ppt-html/assets/pptx-export.js +1944 -1944
  26. package/top-ppt-html/assets/style-gallery.html +589 -589
  27. package/top-ppt-html/assets/templates/architecture.html +3728 -3728
  28. package/top-ppt-html/assets/templates/engine.css +840 -840
  29. package/top-ppt-html/assets/templates/presentation.html +3738 -3738
  30. package/top-ppt-html/assets/templates/research.html +4017 -4017
  31. package/top-ppt-html/assets/templates/ui.js +520 -520
  32. package/top-ppt-html/evals/prompts.csv +15 -15
  33. package/top-ppt-html/evals/rubric.schema.json +25 -25
  34. package/top-ppt-html/evals/run_evals.py +220 -220
  35. package/top-ppt-html/evals/trace.example.json +16 -16
  36. package/top-ppt-html/package-lock.json +207 -0
  37. package/top-ppt-html/package.json +35 -35
  38. package/top-ppt-html/references/charts-basic.md +624 -624
  39. package/top-ppt-html/references/charts-discipline.md +108 -108
  40. package/top-ppt-html/references/charts-extended.md +482 -482
  41. package/top-ppt-html/references/charts.md +28 -28
  42. package/top-ppt-html/references/components-atoms.md +624 -624
  43. package/top-ppt-html/references/components.md +30 -30
  44. package/top-ppt-html/references/content-rules.md +490 -490
  45. package/top-ppt-html/references/design-system-engine.md +235 -235
  46. package/top-ppt-html/references/design-system.md +478 -478
  47. package/top-ppt-html/references/failure-modes.md +214 -214
  48. package/top-ppt-html/references/high-fidelity.md +127 -127
  49. package/top-ppt-html/references/icons.md +397 -397
  50. package/top-ppt-html/references/industry-benchmark.md +105 -105
  51. package/top-ppt-html/references/infographics-stats.md +308 -308
  52. package/top-ppt-html/references/infographics-structure.md +226 -226
  53. package/top-ppt-html/references/infographics.md +43 -43
  54. package/top-ppt-html/references/layout-grammar.md +315 -315
  55. package/top-ppt-html/references/layouts-architecture.md +108 -108
  56. package/top-ppt-html/references/layouts-combo.md +600 -600
  57. package/top-ppt-html/references/layouts-research.md +160 -160
  58. package/top-ppt-html/references/modes.md +254 -254
  59. package/top-ppt-html/references/outline-design.md +275 -275
  60. package/top-ppt-html/references/playbook.md +266 -266
  61. package/top-ppt-html/references/pptx-export.md +209 -209
  62. package/top-ppt-html/references/reform-plan.md +252 -252
  63. package/top-ppt-html/references/styles.md +370 -370
  64. package/top-ppt-html/references/tech-design.md +138 -138
  65. package/top-ppt-html/scripts/audit_css.py +109 -109
  66. package/top-ppt-html/scripts/audit_docs.py +176 -176
  67. package/top-ppt-html/scripts/audit_skill.py +220 -220
  68. package/top-ppt-html/scripts/audit_styles.py +351 -351
  69. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  70. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  71. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  72. package/top-ppt-html/scripts/checks_html.py +127 -127
  73. package/top-ppt-html/scripts/cross_verify.py +294 -294
  74. package/top-ppt-html/scripts/env_probe.py +158 -158
  75. package/top-ppt-html/scripts/extract_model.py +210 -210
  76. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  77. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  78. package/top-ppt-html/scripts/layout-constants.json +3377 -3377
  79. package/top-ppt-html/scripts/layout_slots.json +830 -830
  80. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  81. package/top-ppt-html/scripts/measure_height.py +178 -178
  82. package/top-ppt-html/scripts/model-schema.json +547 -547
  83. package/top-ppt-html/scripts/negative_tests.py +307 -307
  84. package/top-ppt-html/scripts/package_skill.py +291 -291
  85. package/top-ppt-html/scripts/prepare_images.py +341 -341
  86. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  87. package/top-ppt-html/scripts/quality_gate.py +301 -301
  88. package/top-ppt-html/scripts/regression.py +308 -308
  89. package/top-ppt-html/scripts/render_compare.py +275 -275
  90. package/top-ppt-html/scripts/render_from_model.py +698 -698
  91. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  92. package/top-ppt-html/scripts/section-file-map.json +104 -104
  93. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  94. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  95. package/top-ppt-html/scripts/validate_report.py +1456 -1456
@@ -1,214 +1,214 @@
1
- /* TopPPT HTML · A 通道(预览序列化引擎)PPTX 产物生成器(回归内部质量路径):
2
- * Node 加载浏览器预览运行时(assets/pptx-export.js,仅供页面 WYSIWYG 预览),
3
- * 用其 slidesOf/slideXml 序列化输出组装完整 PPTX 包(content types / presentation /
4
- * master / layout / theme / slides + store-only ZIP)。
5
- *
6
- * 为什么在这里组装:页面不再提供 PPTX 导出(仅预览 + 提示词),打包骨架从
7
- * 浏览器运行时移驻本脚本;A 通道产物仅供 regression.py / cross_verify.py 做
8
- * 「预览即交付」双裁判(strict + python-pptx + 与 B 通道逐页文本交叉一致)。
9
- * 用法:NODE_PATH=<pptxgenjs 所在 node_modules 可不需要> node scripts/gen_channel_a.js
10
- * node scripts/gen_channel_a.js --model=<x.model.json> [--out=<dir>] # 单模型(回归探针用)
11
- */
12
- const fs = require('fs');
13
- const path = require('path');
14
- const TopPptHtml = require('../assets/pptx-export.js');
15
- const EX = path.join(__dirname, '..', 'assets', 'examples');
16
- const OUT = path.join(__dirname, '..', 'dist', 'regression-a');
17
-
18
- /* ── 常量(与运行时注入块同值;仅打包用,页面几何以运行时为准) ── */
19
- const PW = 13.333, PH = 7.5;
20
- const EMU = 914400;
21
- const E = (v) => Math.round(v * EMU);
22
- const { NS, RT, esc, solid, PRESETS, PRESETS_DARK } = TopPptHtml;
23
- const relsXml = TopPptHtml.relsXml;
24
- const slideXml = TopPptHtml.slideXml;
25
- const slidesOf = TopPptHtml.slidesOf;
26
- const dataColors = TopPptHtml.dataColors; /* 编码色板(单源 styleDataColors,裸 hex) */
27
-
28
- /* ── 完整 PPTX 包组装(原浏览器 buildPptx 骨架,一字未改语义) ── */
29
- function buildPptx(model) {
30
- /* theme="dark" 用 PRESETS_DARK(与 B 通道 build_pptx.js 同规则,保证双裁判一致) */
31
- const T = (model.theme === 'dark' && PRESETS_DARK && Object.keys(PRESETS_DARK).length)
32
- ? PRESETS_DARK : PRESETS;
33
- const S = T[model.style] || PRESETS['business-blue'];
34
- const slides = slidesOf(model, S);
35
- const files = {};
36
- /* 主题 accent2–accent5 取自该风格自己的编码色板 c2–c5(单源 styleDataColors,
37
- 与 B 通道 dataColors() 同源)——不再硬编码某一套风格的数据色。 */
38
- const dc = (typeof dataColors === 'function')
39
- ? (dataColors(model.style, model.theme === 'dark' ? 'dark' : 'light') || []) : [];
40
- const A2 = dc[1] || S.faint, A3 = dc[2] || S.line, A4 = dc[3] || S.soft, A5 = dc[4] || S.body;
41
-
42
- const slideOverrides = slides.map((_, i) =>
43
- '<Override PartName="/ppt/slides/slide' + (i + 1) + '.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>').join('');
44
- files['[Content_Types].xml'] =
45
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
46
- '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
47
- '<Default Extension="xml" ContentType="application/xml"/>' +
48
- '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
49
- '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>' +
50
- '<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>' +
51
- '<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>' +
52
- '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>' +
53
- '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
54
- '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
55
- slideOverrides + '</Types>';
56
-
57
- files['_rels/.rels'] = relsXml([
58
- ['rId1', RT + 'officeDocument', 'ppt/presentation.xml'],
59
- ['rId2', 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', 'docProps/core.xml'],
60
- ['rId3', RT + 'extended-properties', 'docProps/app.xml']
61
- ]);
62
- files['docProps/core.xml'] =
63
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
64
- '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ' +
65
- 'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" ' +
66
- 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
67
- '<dc:title>' + esc(model.title || 'TopPPT HTML 报告') + '</dc:title>' +
68
- '<dc:creator>TopPPT HTML</dc:creator>' +
69
- '<dcterms:created xsi:type="dcterms:W3CDTF">' + new Date().toISOString() + '</dcterms:created></cp:coreProperties>';
70
- files['docProps/app.xml'] =
71
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
72
- '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">' +
73
- '<Application>TopPPT HTML</Application><Slides>' + slides.length + '</Slides></Properties>';
74
-
75
- const sldIds = slides.map((_, i) => '<p:sldId id="' + (256 + i) + '" r:id="rId' + (i + 2) + '"/>').join('');
76
- files['ppt/presentation.xml'] =
77
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
78
- '<p:presentation xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
79
- '<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>' +
80
- '<p:sldIdLst>' + sldIds + '</p:sldIdLst>' +
81
- '<p:sldSz cx="' + E(PW) + '" cy="' + E(PH) + '"/><p:notesSz cx="6858000" cy="9144000"/></p:presentation>';
82
- const presRels = [['rId1', RT + 'slideMaster', 'slideMasters/slideMaster1.xml']];
83
- slides.forEach((_, i) => presRels.push(['rId' + (i + 2), RT + 'slide', 'slides/slide' + (i + 1) + '.xml']));
84
- /* OPC 规范:part 的 rels 必须位于同目录 _rels/ 子目录(ppt/_rels/presentation.xml.rels)。
85
- python-pptx 严格按此路径解析(PowerPoint 宽容但规范路径才是正解)。 */
86
- files['ppt/_rels/presentation.xml.rels'] = relsXml(presRels);
87
-
88
- files['ppt/slideMasters/slideMaster1.xml'] =
89
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
90
- '<p:sldMaster xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
91
- '<p:cSld><p:bg><p:bgPr>' + solid(S.bg) + '<a:effectLst/></p:bgPr></p:bg>' +
92
- '<p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
93
- '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>' +
94
- '<p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>' +
95
- '<p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>' +
96
- '<p:txStyles><p:titleStyle><a:lvl1pPr><a:defRPr sz="4400" b="1"/></a:lvl1pPr></p:titleStyle>' +
97
- '<p:bodyStyle><a:lvl1pPr><a:defRPr sz="1800"/></a:lvl1pPr></p:bodyStyle>' +
98
- '<p:otherStyle><a:lvl1pPr><a:defRPr sz="1800"/></a:lvl1pPr></p:otherStyle></p:txStyles></p:sldMaster>';
99
- files['ppt/slideMasters/_rels/slideMaster1.xml.rels'] = relsXml([
100
- ['rId1', RT + 'slideLayout', '../slideLayouts/slideLayout1.xml'],
101
- ['rId2', RT + 'theme', '../theme/theme1.xml']
102
- ]);
103
- files['ppt/slideLayouts/slideLayout1.xml'] =
104
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
105
- '<p:sldLayout xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '" type="blank" preserve="1">' +
106
- '<p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
107
- '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>' +
108
- '<p:clrMapOvr><a:overrideClrMapping bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:clrMapOvr></p:sldLayout>';
109
- files['ppt/slideLayouts/_rels/slideLayout1.xml.rels'] = relsXml([
110
- ['rId1', RT + 'slideMaster', '../slideMasters/slideMaster1.xml']
111
- ]);
112
- files['ppt/theme/theme1.xml'] =
113
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
114
- '<a:theme xmlns:a="' + NS.a + '" name="TopPPT HTML">' +
115
- '<a:themeElements><a:clrScheme name="TopPPT HTML">' +
116
- '<a:dk1><a:srgbClr val="' + S.ink + '"/></a:dk1><a:lt1><a:srgbClr val="' + S.bg + '"/></a:lt1>' +
117
- '<a:dk2><a:srgbClr val="' + S.body + '"/></a:dk2><a:lt2><a:srgbClr val="' + S.surface + '"/></a:lt2>' +
118
- '<a:accent1><a:srgbClr val="' + S.accent + '"/></a:accent1>' +
119
- '<a:accent2><a:srgbClr val="' + A2 + '"/></a:accent2><a:accent3><a:srgbClr val="' + A3 + '"/></a:accent3>' +
120
- '<a:accent4><a:srgbClr val="' + A4 + '"/></a:accent4><a:accent5><a:srgbClr val="' + A5 + '"/></a:accent5>' +
121
- '<a:accent6><a:srgbClr val="' + S.faint + '"/></a:accent6>' +
122
- '<a:hlink><a:srgbClr val="' + S.accent + '"/></a:hlink><a:folHlink><a:srgbClr val="' + S.faint + '"/></a:folHlink>' +
123
- '</a:clrScheme><a:fontScheme name="TopPPT HTML"><a:majorFont><a:latin typeface="' + S.fontDisplay + '"/><a:ea typeface="' + S.fontDisplay + '"/></a:majorFont>' +
124
- '<a:minorFont><a:latin typeface="' + S.font + '"/><a:ea typeface="' + S.font + '"/></a:minorFont></a:fontScheme>' +
125
- '<a:fmtScheme name="TopPPT HTML"><a:fillStyleLst>' + solid(S.accent) + solid(S.surface) + solid(S.soft) + '</a:fillStyleLst>' +
126
- '<a:lnStyleLst><a:ln w="9525">' + solid(S.line) + '</a:ln><a:ln w="25400">' + solid(S.line) + '</a:ln><a:ln w="38100">' + solid(S.line) + '</a:ln></a:lnStyleLst>' +
127
- '<a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>' +
128
- '<a:bgFillStyleLst>' + solid(S.bg) + solid(S.surface) + solid(S.soft) + '</a:bgFillStyleLst></a:fmtScheme>' +
129
- '</a:themeElements></a:theme>';
130
-
131
- slides.forEach((sh, i) => {
132
- files['ppt/slides/slide' + (i + 1) + '.xml'] = slideXml(sh);
133
- files['ppt/slides/_rels/slide' + (i + 1) + '.xml.rels'] = relsXml([
134
- ['rId1', RT + 'slideLayout', '../slideLayouts/slideLayout1.xml']
135
- ]);
136
- });
137
-
138
- return zipStore(files);
139
- }
140
-
141
- /* ── store-only ZIP(无压缩、合法、PowerPoint 可开;Node 版,逻辑与原浏览器实现一致) ── */
142
- const CRC_T = (() => {
143
- const t = new Uint32Array(256);
144
- for (let n = 0; n < 256; n++) {
145
- let c = n;
146
- for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
147
- t[n] = c >>> 0;
148
- }
149
- return t;
150
- })();
151
- function crc32(buf) {
152
- let c = 0xFFFFFFFF;
153
- for (let i = 0; i < buf.length; i++) c = CRC_T[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
154
- return (c ^ 0xFFFFFFFF) >>> 0;
155
- }
156
- function zipStore(files) {
157
- const enc = new TextEncoder();
158
- const names = Object.keys(files);
159
- const chunks = [], central = [];
160
- let offset = 0;
161
- names.forEach((name) => {
162
- const nb = enc.encode(name), data = enc.encode(files[name]);
163
- const crc = crc32(data);
164
- const lh = new DataView(new ArrayBuffer(30));
165
- lh.setUint32(0, 0x04034b50, true); lh.setUint16(4, 20, true); lh.setUint16(6, 0x0800, true);
166
- lh.setUint16(8, 0, true); lh.setUint16(10, 0, true); lh.setUint16(12, 0x21, true);
167
- lh.setUint32(14, crc, true); lh.setUint32(18, data.length, true); lh.setUint32(22, data.length, true);
168
- lh.setUint16(26, nb.length, true); lh.setUint16(28, 0, true);
169
- chunks.push(new Uint8Array(lh.buffer), nb, data);
170
- const ch = new DataView(new ArrayBuffer(46));
171
- ch.setUint32(0, 0x02014b50, true); ch.setUint16(4, 20, true); ch.setUint16(6, 20, true);
172
- ch.setUint16(8, 0x0800, true); ch.setUint16(10, 0, true); ch.setUint16(12, 0, true);
173
- ch.setUint16(14, 0x21, true); ch.setUint32(16, crc, true);
174
- ch.setUint32(20, data.length, true); ch.setUint32(24, data.length, true);
175
- ch.setUint16(28, nb.length, true); ch.setUint32(42, offset, true);
176
- central.push(new Uint8Array(ch.buffer), nb);
177
- offset += 30 + nb.length + data.length;
178
- });
179
- const cdSize = central.reduce((a, c) => a + c.length, 0);
180
- const end = new DataView(new ArrayBuffer(22));
181
- end.setUint32(0, 0x06054b50, true);
182
- end.setUint16(8, names.length, true); end.setUint16(10, names.length, true);
183
- end.setUint32(12, cdSize, true); end.setUint32(16, offset, true);
184
- const total = offset + cdSize + 22;
185
- const out = new Uint8Array(total);
186
- let pos = 0;
187
- chunks.concat(central, [new Uint8Array(end.buffer)]).forEach((c) => {
188
- out.set(c, pos); pos += c.length;
189
- });
190
- return out;
191
- }
192
-
193
- /* ── 输入 / 输出 ──
194
- 默认:示例矩阵(assets/examples/*.model.json)→ dist/regression-a/。
195
- --model=<file> [--out=<dir>]:对任意模型跑 A 通道,供回归探针(split 组合页)与
196
- cross_verify.py 复用同一套「预览即交付」双裁判。 */
197
- const argv = process.argv.slice(2);
198
- const modelArg = (argv.find((a) => a.startsWith('--model=')) || '').split('=')[1];
199
- const outArg = (argv.find((a) => a.startsWith('--out=')) || '').split('=')[1];
200
- const OUT_DIR = outArg ? path.resolve(outArg) : OUT;
201
- fs.mkdirSync(OUT_DIR, { recursive: true });
202
-
203
- const jobs = modelArg
204
- ? [[path.resolve(modelArg), path.basename(modelArg).replace(/\.model\.json$/, '.pptx')]]
205
- : fs.readdirSync(EX).filter((f) => f.endsWith('.model.json')).sort()
206
- .map((f) => [path.join(EX, f), f.replace('.model.json', '.pptx')]);
207
-
208
- for (const [src, name] of jobs) {
209
- const model = JSON.parse(fs.readFileSync(src, 'utf-8'));
210
- const bytes = buildPptx(model);
211
- const out = path.join(OUT_DIR, name);
212
- fs.writeFileSync(out, Buffer.from(bytes));
213
- console.log('A-channel:', path.basename(out), Math.round(bytes.length / 1024) + 'KB');
214
- }
1
+ /* TopPPT HTML · A 通道(预览序列化引擎)PPTX 产物生成器(回归内部质量路径):
2
+ * Node 加载浏览器预览运行时(assets/pptx-export.js,仅供页面 WYSIWYG 预览),
3
+ * 用其 slidesOf/slideXml 序列化输出组装完整 PPTX 包(content types / presentation /
4
+ * master / layout / theme / slides + store-only ZIP)。
5
+ *
6
+ * 为什么在这里组装:页面不再提供 PPTX 导出(仅预览 + 提示词),打包骨架从
7
+ * 浏览器运行时移驻本脚本;A 通道产物仅供 regression.py / cross_verify.py 做
8
+ * 「预览即交付」双裁判(strict + python-pptx + 与 B 通道逐页文本交叉一致)。
9
+ * 用法:NODE_PATH=<pptxgenjs 所在 node_modules 可不需要> node scripts/gen_channel_a.js
10
+ * node scripts/gen_channel_a.js --model=<x.model.json> [--out=<dir>] # 单模型(回归探针用)
11
+ */
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const TopPptHtml = require('../assets/pptx-export.js');
15
+ const EX = path.join(__dirname, '..', 'assets', 'examples');
16
+ const OUT = path.join(__dirname, '..', 'dist', 'regression-a');
17
+
18
+ /* ── 常量(与运行时注入块同值;仅打包用,页面几何以运行时为准) ── */
19
+ const PW = 13.333, PH = 7.5;
20
+ const EMU = 914400;
21
+ const E = (v) => Math.round(v * EMU);
22
+ const { NS, RT, esc, solid, PRESETS, PRESETS_DARK } = TopPptHtml;
23
+ const relsXml = TopPptHtml.relsXml;
24
+ const slideXml = TopPptHtml.slideXml;
25
+ const slidesOf = TopPptHtml.slidesOf;
26
+ const dataColors = TopPptHtml.dataColors; /* 编码色板(单源 styleDataColors,裸 hex) */
27
+
28
+ /* ── 完整 PPTX 包组装(原浏览器 buildPptx 骨架,一字未改语义) ── */
29
+ function buildPptx(model) {
30
+ /* theme="dark" 用 PRESETS_DARK(与 B 通道 build_pptx.js 同规则,保证双裁判一致) */
31
+ const T = (model.theme === 'dark' && PRESETS_DARK && Object.keys(PRESETS_DARK).length)
32
+ ? PRESETS_DARK : PRESETS;
33
+ const S = T[model.style] || PRESETS['business-blue'];
34
+ const slides = slidesOf(model, S);
35
+ const files = {};
36
+ /* 主题 accent2–accent5 取自该风格自己的编码色板 c2–c5(单源 styleDataColors,
37
+ 与 B 通道 dataColors() 同源)——不再硬编码某一套风格的数据色。 */
38
+ const dc = (typeof dataColors === 'function')
39
+ ? (dataColors(model.style, model.theme === 'dark' ? 'dark' : 'light') || []) : [];
40
+ const A2 = dc[1] || S.faint, A3 = dc[2] || S.line, A4 = dc[3] || S.soft, A5 = dc[4] || S.body;
41
+
42
+ const slideOverrides = slides.map((_, i) =>
43
+ '<Override PartName="/ppt/slides/slide' + (i + 1) + '.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>').join('');
44
+ files['[Content_Types].xml'] =
45
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
46
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
47
+ '<Default Extension="xml" ContentType="application/xml"/>' +
48
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
49
+ '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>' +
50
+ '<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>' +
51
+ '<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>' +
52
+ '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>' +
53
+ '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
54
+ '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
55
+ slideOverrides + '</Types>';
56
+
57
+ files['_rels/.rels'] = relsXml([
58
+ ['rId1', RT + 'officeDocument', 'ppt/presentation.xml'],
59
+ ['rId2', 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', 'docProps/core.xml'],
60
+ ['rId3', RT + 'extended-properties', 'docProps/app.xml']
61
+ ]);
62
+ files['docProps/core.xml'] =
63
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
64
+ '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ' +
65
+ 'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" ' +
66
+ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
67
+ '<dc:title>' + esc(model.title || 'TopPPT HTML 报告') + '</dc:title>' +
68
+ '<dc:creator>TopPPT HTML</dc:creator>' +
69
+ '<dcterms:created xsi:type="dcterms:W3CDTF">' + new Date().toISOString() + '</dcterms:created></cp:coreProperties>';
70
+ files['docProps/app.xml'] =
71
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
72
+ '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">' +
73
+ '<Application>TopPPT HTML</Application><Slides>' + slides.length + '</Slides></Properties>';
74
+
75
+ const sldIds = slides.map((_, i) => '<p:sldId id="' + (256 + i) + '" r:id="rId' + (i + 2) + '"/>').join('');
76
+ files['ppt/presentation.xml'] =
77
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
78
+ '<p:presentation xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
79
+ '<p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>' +
80
+ '<p:sldIdLst>' + sldIds + '</p:sldIdLst>' +
81
+ '<p:sldSz cx="' + E(PW) + '" cy="' + E(PH) + '"/><p:notesSz cx="6858000" cy="9144000"/></p:presentation>';
82
+ const presRels = [['rId1', RT + 'slideMaster', 'slideMasters/slideMaster1.xml']];
83
+ slides.forEach((_, i) => presRels.push(['rId' + (i + 2), RT + 'slide', 'slides/slide' + (i + 1) + '.xml']));
84
+ /* OPC 规范:part 的 rels 必须位于同目录 _rels/ 子目录(ppt/_rels/presentation.xml.rels)。
85
+ python-pptx 严格按此路径解析(PowerPoint 宽容但规范路径才是正解)。 */
86
+ files['ppt/_rels/presentation.xml.rels'] = relsXml(presRels);
87
+
88
+ files['ppt/slideMasters/slideMaster1.xml'] =
89
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
90
+ '<p:sldMaster xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
91
+ '<p:cSld><p:bg><p:bgPr>' + solid(S.bg) + '<a:effectLst/></p:bgPr></p:bg>' +
92
+ '<p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
93
+ '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>' +
94
+ '<p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>' +
95
+ '<p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>' +
96
+ '<p:txStyles><p:titleStyle><a:lvl1pPr><a:defRPr sz="4400" b="1"/></a:lvl1pPr></p:titleStyle>' +
97
+ '<p:bodyStyle><a:lvl1pPr><a:defRPr sz="1800"/></a:lvl1pPr></p:bodyStyle>' +
98
+ '<p:otherStyle><a:lvl1pPr><a:defRPr sz="1800"/></a:lvl1pPr></p:otherStyle></p:txStyles></p:sldMaster>';
99
+ files['ppt/slideMasters/_rels/slideMaster1.xml.rels'] = relsXml([
100
+ ['rId1', RT + 'slideLayout', '../slideLayouts/slideLayout1.xml'],
101
+ ['rId2', RT + 'theme', '../theme/theme1.xml']
102
+ ]);
103
+ files['ppt/slideLayouts/slideLayout1.xml'] =
104
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
105
+ '<p:sldLayout xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '" type="blank" preserve="1">' +
106
+ '<p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
107
+ '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>' +
108
+ '<p:clrMapOvr><a:overrideClrMapping bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:clrMapOvr></p:sldLayout>';
109
+ files['ppt/slideLayouts/_rels/slideLayout1.xml.rels'] = relsXml([
110
+ ['rId1', RT + 'slideMaster', '../slideMasters/slideMaster1.xml']
111
+ ]);
112
+ files['ppt/theme/theme1.xml'] =
113
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
114
+ '<a:theme xmlns:a="' + NS.a + '" name="TopPPT HTML">' +
115
+ '<a:themeElements><a:clrScheme name="TopPPT HTML">' +
116
+ '<a:dk1><a:srgbClr val="' + S.ink + '"/></a:dk1><a:lt1><a:srgbClr val="' + S.bg + '"/></a:lt1>' +
117
+ '<a:dk2><a:srgbClr val="' + S.body + '"/></a:dk2><a:lt2><a:srgbClr val="' + S.surface + '"/></a:lt2>' +
118
+ '<a:accent1><a:srgbClr val="' + S.accent + '"/></a:accent1>' +
119
+ '<a:accent2><a:srgbClr val="' + A2 + '"/></a:accent2><a:accent3><a:srgbClr val="' + A3 + '"/></a:accent3>' +
120
+ '<a:accent4><a:srgbClr val="' + A4 + '"/></a:accent4><a:accent5><a:srgbClr val="' + A5 + '"/></a:accent5>' +
121
+ '<a:accent6><a:srgbClr val="' + S.faint + '"/></a:accent6>' +
122
+ '<a:hlink><a:srgbClr val="' + S.accent + '"/></a:hlink><a:folHlink><a:srgbClr val="' + S.faint + '"/></a:folHlink>' +
123
+ '</a:clrScheme><a:fontScheme name="TopPPT HTML"><a:majorFont><a:latin typeface="' + S.fontDisplay + '"/><a:ea typeface="' + S.fontDisplay + '"/></a:majorFont>' +
124
+ '<a:minorFont><a:latin typeface="' + S.font + '"/><a:ea typeface="' + S.font + '"/></a:minorFont></a:fontScheme>' +
125
+ '<a:fmtScheme name="TopPPT HTML"><a:fillStyleLst>' + solid(S.accent) + solid(S.surface) + solid(S.soft) + '</a:fillStyleLst>' +
126
+ '<a:lnStyleLst><a:ln w="9525">' + solid(S.line) + '</a:ln><a:ln w="25400">' + solid(S.line) + '</a:ln><a:ln w="38100">' + solid(S.line) + '</a:ln></a:lnStyleLst>' +
127
+ '<a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>' +
128
+ '<a:bgFillStyleLst>' + solid(S.bg) + solid(S.surface) + solid(S.soft) + '</a:bgFillStyleLst></a:fmtScheme>' +
129
+ '</a:themeElements></a:theme>';
130
+
131
+ slides.forEach((sh, i) => {
132
+ files['ppt/slides/slide' + (i + 1) + '.xml'] = slideXml(sh);
133
+ files['ppt/slides/_rels/slide' + (i + 1) + '.xml.rels'] = relsXml([
134
+ ['rId1', RT + 'slideLayout', '../slideLayouts/slideLayout1.xml']
135
+ ]);
136
+ });
137
+
138
+ return zipStore(files);
139
+ }
140
+
141
+ /* ── store-only ZIP(无压缩、合法、PowerPoint 可开;Node 版,逻辑与原浏览器实现一致) ── */
142
+ const CRC_T = (() => {
143
+ const t = new Uint32Array(256);
144
+ for (let n = 0; n < 256; n++) {
145
+ let c = n;
146
+ for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
147
+ t[n] = c >>> 0;
148
+ }
149
+ return t;
150
+ })();
151
+ function crc32(buf) {
152
+ let c = 0xFFFFFFFF;
153
+ for (let i = 0; i < buf.length; i++) c = CRC_T[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
154
+ return (c ^ 0xFFFFFFFF) >>> 0;
155
+ }
156
+ function zipStore(files) {
157
+ const enc = new TextEncoder();
158
+ const names = Object.keys(files);
159
+ const chunks = [], central = [];
160
+ let offset = 0;
161
+ names.forEach((name) => {
162
+ const nb = enc.encode(name), data = enc.encode(files[name]);
163
+ const crc = crc32(data);
164
+ const lh = new DataView(new ArrayBuffer(30));
165
+ lh.setUint32(0, 0x04034b50, true); lh.setUint16(4, 20, true); lh.setUint16(6, 0x0800, true);
166
+ lh.setUint16(8, 0, true); lh.setUint16(10, 0, true); lh.setUint16(12, 0x21, true);
167
+ lh.setUint32(14, crc, true); lh.setUint32(18, data.length, true); lh.setUint32(22, data.length, true);
168
+ lh.setUint16(26, nb.length, true); lh.setUint16(28, 0, true);
169
+ chunks.push(new Uint8Array(lh.buffer), nb, data);
170
+ const ch = new DataView(new ArrayBuffer(46));
171
+ ch.setUint32(0, 0x02014b50, true); ch.setUint16(4, 20, true); ch.setUint16(6, 20, true);
172
+ ch.setUint16(8, 0x0800, true); ch.setUint16(10, 0, true); ch.setUint16(12, 0, true);
173
+ ch.setUint16(14, 0x21, true); ch.setUint32(16, crc, true);
174
+ ch.setUint32(20, data.length, true); ch.setUint32(24, data.length, true);
175
+ ch.setUint16(28, nb.length, true); ch.setUint32(42, offset, true);
176
+ central.push(new Uint8Array(ch.buffer), nb);
177
+ offset += 30 + nb.length + data.length;
178
+ });
179
+ const cdSize = central.reduce((a, c) => a + c.length, 0);
180
+ const end = new DataView(new ArrayBuffer(22));
181
+ end.setUint32(0, 0x06054b50, true);
182
+ end.setUint16(8, names.length, true); end.setUint16(10, names.length, true);
183
+ end.setUint32(12, cdSize, true); end.setUint32(16, offset, true);
184
+ const total = offset + cdSize + 22;
185
+ const out = new Uint8Array(total);
186
+ let pos = 0;
187
+ chunks.concat(central, [new Uint8Array(end.buffer)]).forEach((c) => {
188
+ out.set(c, pos); pos += c.length;
189
+ });
190
+ return out;
191
+ }
192
+
193
+ /* ── 输入 / 输出 ──
194
+ 默认:示例矩阵(assets/examples/*.model.json)→ dist/regression-a/。
195
+ --model=<file> [--out=<dir>]:对任意模型跑 A 通道,供回归探针(split 组合页)与
196
+ cross_verify.py 复用同一套「预览即交付」双裁判。 */
197
+ const argv = process.argv.slice(2);
198
+ const modelArg = (argv.find((a) => a.startsWith('--model=')) || '').split('=')[1];
199
+ const outArg = (argv.find((a) => a.startsWith('--out=')) || '').split('=')[1];
200
+ const OUT_DIR = outArg ? path.resolve(outArg) : OUT;
201
+ fs.mkdirSync(OUT_DIR, { recursive: true });
202
+
203
+ const jobs = modelArg
204
+ ? [[path.resolve(modelArg), path.basename(modelArg).replace(/\.model\.json$/, '.pptx')]]
205
+ : fs.readdirSync(EX).filter((f) => f.endsWith('.model.json')).sort()
206
+ .map((f) => [path.join(EX, f), f.replace('.model.json', '.pptx')]);
207
+
208
+ for (const [src, name] of jobs) {
209
+ const model = JSON.parse(fs.readFileSync(src, 'utf-8'));
210
+ const bytes = buildPptx(model);
211
+ const out = path.join(OUT_DIR, name);
212
+ fs.writeFileSync(out, Buffer.from(bytes));
213
+ console.log('A-channel:', path.basename(out), Math.round(bytes.length / 1024) + 'KB');
214
+ }