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