@topmindspace/tms-skills 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -134
- package/README.md +1 -1
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -254
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
- package/top-ppt-html/assets/pptx-export.js +1944 -1944
- package/top-ppt-html/assets/style-gallery.html +589 -589
- package/top-ppt-html/assets/templates/architecture.html +3728 -3728
- package/top-ppt-html/assets/templates/engine.css +840 -840
- package/top-ppt-html/assets/templates/presentation.html +3738 -3738
- package/top-ppt-html/assets/templates/research.html +4017 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/evals/prompts.csv +15 -15
- package/top-ppt-html/evals/rubric.schema.json +25 -25
- package/top-ppt-html/evals/run_evals.py +220 -220
- package/top-ppt-html/evals/trace.example.json +16 -16
- package/top-ppt-html/package-lock.json +207 -0
- package/top-ppt-html/package.json +35 -35
- package/top-ppt-html/references/charts-basic.md +624 -624
- package/top-ppt-html/references/charts-discipline.md +108 -108
- package/top-ppt-html/references/charts-extended.md +482 -482
- package/top-ppt-html/references/charts.md +28 -28
- package/top-ppt-html/references/components-atoms.md +624 -624
- package/top-ppt-html/references/components.md +30 -30
- package/top-ppt-html/references/content-rules.md +490 -490
- package/top-ppt-html/references/design-system-engine.md +235 -235
- package/top-ppt-html/references/design-system.md +478 -478
- package/top-ppt-html/references/failure-modes.md +214 -214
- package/top-ppt-html/references/high-fidelity.md +127 -127
- package/top-ppt-html/references/icons.md +397 -397
- package/top-ppt-html/references/industry-benchmark.md +105 -105
- package/top-ppt-html/references/infographics-stats.md +308 -308
- package/top-ppt-html/references/infographics-structure.md +226 -226
- package/top-ppt-html/references/infographics.md +43 -43
- package/top-ppt-html/references/layout-grammar.md +315 -315
- package/top-ppt-html/references/layouts-architecture.md +108 -108
- package/top-ppt-html/references/layouts-combo.md +600 -600
- package/top-ppt-html/references/layouts-research.md +160 -160
- package/top-ppt-html/references/modes.md +254 -254
- package/top-ppt-html/references/outline-design.md +275 -275
- package/top-ppt-html/references/playbook.md +266 -266
- package/top-ppt-html/references/pptx-export.md +209 -209
- package/top-ppt-html/references/reform-plan.md +252 -252
- package/top-ppt-html/references/styles.md +370 -370
- package/top-ppt-html/references/tech-design.md +138 -138
- package/top-ppt-html/scripts/audit_css.py +109 -109
- package/top-ppt-html/scripts/audit_docs.py +176 -176
- package/top-ppt-html/scripts/audit_skill.py +220 -220
- package/top-ppt-html/scripts/audit_styles.py +351 -351
- package/top-ppt-html/scripts/build_examples.py +2276 -2276
- package/top-ppt-html/scripts/build_pptx.js +2380 -2380
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
- package/top-ppt-html/scripts/checks_html.py +127 -127
- package/top-ppt-html/scripts/cross_verify.py +294 -294
- package/top-ppt-html/scripts/env_probe.py +158 -158
- package/top-ppt-html/scripts/extract_model.py +210 -210
- package/top-ppt-html/scripts/extract_snippet.py +374 -374
- package/top-ppt-html/scripts/gen_channel_a.js +214 -214
- package/top-ppt-html/scripts/layout-constants.json +3377 -3377
- package/top-ppt-html/scripts/layout_slots.json +830 -830
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
- package/top-ppt-html/scripts/measure_height.py +178 -178
- package/top-ppt-html/scripts/model-schema.json +547 -547
- package/top-ppt-html/scripts/negative_tests.py +307 -307
- package/top-ppt-html/scripts/package_skill.py +291 -291
- package/top-ppt-html/scripts/prepare_images.py +341 -341
- package/top-ppt-html/scripts/probe_image_export.py +188 -188
- package/top-ppt-html/scripts/quality_gate.py +301 -301
- package/top-ppt-html/scripts/regression.py +308 -308
- package/top-ppt-html/scripts/render_compare.py +275 -275
- package/top-ppt-html/scripts/render_from_model.py +698 -698
- package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
- package/top-ppt-html/scripts/section-file-map.json +104 -104
- package/top-ppt-html/scripts/sync_runtime.py +662 -662
- package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
- 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
|
+
}
|