@topmindspace/tms-skills 2.0.0 → 2.0.2

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 (99) hide show
  1. package/CHANGELOG.md +139 -123
  2. package/README.md +126 -103
  3. package/bin/tms-skills.js +160 -160
  4. package/package.json +48 -48
  5. package/top-ppt-html/README.md +254 -253
  6. package/top-ppt-html/SKILL.md +111 -111
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3873 -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 +3873 -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 +4272 -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 +4272 -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 +4272 -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 +5474 -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 +5474 -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 +5474 -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 +5474 -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 +559 -589
  27. package/top-ppt-html/assets/templates/architecture.html +3675 -3728
  28. package/top-ppt-html/assets/templates/engine.css +787 -840
  29. package/top-ppt-html/assets/templates/presentation.html +3685 -3738
  30. package/top-ppt-html/assets/templates/research.html +3964 -4017
  31. package/top-ppt-html/assets/templates/ui.js +520 -520
  32. package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
  33. package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
  34. package/top-ppt-html/assets/theme-overview-research.png +0 -0
  35. package/top-ppt-html/assets/theme-overview.png +0 -0
  36. package/top-ppt-html/evals/prompts.csv +15 -15
  37. package/top-ppt-html/evals/rubric.schema.json +25 -25
  38. package/top-ppt-html/evals/run_evals.py +220 -220
  39. package/top-ppt-html/evals/trace.example.json +16 -16
  40. package/top-ppt-html/package-lock.json +175 -0
  41. package/top-ppt-html/package.json +30 -35
  42. package/top-ppt-html/references/charts-basic.md +624 -624
  43. package/top-ppt-html/references/charts-discipline.md +108 -108
  44. package/top-ppt-html/references/charts-extended.md +482 -482
  45. package/top-ppt-html/references/charts.md +28 -28
  46. package/top-ppt-html/references/components-atoms.md +624 -624
  47. package/top-ppt-html/references/components.md +30 -30
  48. package/top-ppt-html/references/content-rules.md +490 -490
  49. package/top-ppt-html/references/design-system-engine.md +235 -235
  50. package/top-ppt-html/references/design-system.md +471 -478
  51. package/top-ppt-html/references/failure-modes.md +214 -214
  52. package/top-ppt-html/references/high-fidelity.md +127 -127
  53. package/top-ppt-html/references/icons.md +397 -397
  54. package/top-ppt-html/references/industry-benchmark.md +105 -105
  55. package/top-ppt-html/references/infographics-stats.md +308 -308
  56. package/top-ppt-html/references/infographics-structure.md +226 -226
  57. package/top-ppt-html/references/infographics.md +43 -43
  58. package/top-ppt-html/references/layout-grammar.md +315 -315
  59. package/top-ppt-html/references/layouts-architecture.md +108 -108
  60. package/top-ppt-html/references/layouts-combo.md +600 -600
  61. package/top-ppt-html/references/layouts-research.md +160 -160
  62. package/top-ppt-html/references/modes.md +254 -254
  63. package/top-ppt-html/references/outline-design.md +275 -275
  64. package/top-ppt-html/references/playbook.md +266 -266
  65. package/top-ppt-html/references/pptx-export.md +209 -209
  66. package/top-ppt-html/references/reform-plan.md +252 -252
  67. package/top-ppt-html/references/styles.md +336 -370
  68. package/top-ppt-html/references/tech-design.md +138 -138
  69. package/top-ppt-html/scripts/audit_css.py +109 -109
  70. package/top-ppt-html/scripts/audit_docs.py +176 -176
  71. package/top-ppt-html/scripts/audit_skill.py +220 -220
  72. package/top-ppt-html/scripts/audit_styles.py +293 -351
  73. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  74. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  75. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  76. package/top-ppt-html/scripts/checks_html.py +127 -127
  77. package/top-ppt-html/scripts/cross_verify.py +294 -294
  78. package/top-ppt-html/scripts/env_probe.py +158 -158
  79. package/top-ppt-html/scripts/extract_model.py +210 -210
  80. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  81. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  82. package/top-ppt-html/scripts/layout-constants.json +3309 -3377
  83. package/top-ppt-html/scripts/layout_slots.json +830 -830
  84. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  85. package/top-ppt-html/scripts/measure_height.py +178 -178
  86. package/top-ppt-html/scripts/model-schema.json +547 -547
  87. package/top-ppt-html/scripts/negative_tests.py +307 -307
  88. package/top-ppt-html/scripts/package_skill.py +291 -291
  89. package/top-ppt-html/scripts/prepare_images.py +341 -341
  90. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  91. package/top-ppt-html/scripts/quality_gate.py +301 -301
  92. package/top-ppt-html/scripts/regression.py +307 -308
  93. package/top-ppt-html/scripts/render_compare.py +275 -275
  94. package/top-ppt-html/scripts/render_from_model.py +698 -698
  95. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  96. package/top-ppt-html/scripts/section-file-map.json +104 -104
  97. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  98. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  99. 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
+ }