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