@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.
- package/CHANGELOG.md +139 -134
- 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 +3873 -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 +3873 -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 +4272 -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 +4272 -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 +4272 -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 +5474 -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 +5474 -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 +5474 -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 +5474 -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 +559 -589
- package/top-ppt-html/assets/templates/architecture.html +3675 -3728
- package/top-ppt-html/assets/templates/engine.css +787 -840
- package/top-ppt-html/assets/templates/presentation.html +3685 -3738
- package/top-ppt-html/assets/templates/research.html +3964 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
- package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
- package/top-ppt-html/assets/theme-overview-research.png +0 -0
- package/top-ppt-html/assets/theme-overview.png +0 -0
- 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 +175 -0
- package/top-ppt-html/package.json +30 -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 +471 -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 +336 -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 +293 -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 +3309 -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 +307 -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,412 +1,412 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TopPPT HTML · 页型布局区域 IR(双通道几何收敛起点)
|
|
3
|
-
*
|
|
4
|
-
* 从 layout-constants.json + layout_slots.json 派生「语义槽位 → 英寸矩形」。
|
|
5
|
-
* B 通道(build_pptx.js)直接 require;A 通道消费同一 LC 常量(数字同源),
|
|
6
|
-
* 本模块是把「槽位语义」从散落坐标收拢成可测接口的第一步。
|
|
7
|
-
*
|
|
8
|
-
* 纪律:改几何只改 layout-constants.json;改槽位只改 layout_slots.json。
|
|
9
|
-
* 新增页型先扩 layout_slots.json,再在此补 regionOf 分支,最后写双引擎。
|
|
10
|
-
*/
|
|
11
|
-
'use strict';
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
|
|
16
|
-
const ROOT = path.join(__dirname, '..');
|
|
17
|
-
const LC = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout-constants.json'), 'utf8'));
|
|
18
|
-
// 布局 IR:优先 layout-constants.layoutSlots(v9 单源合并);否则回落 layout_slots.json
|
|
19
|
-
let SLOTS = LC.layoutSlots;
|
|
20
|
-
if (!SLOTS || !SLOTS.pageTypes) {
|
|
21
|
-
SLOTS = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout_slots.json'), 'utf8'));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const INFO_TYPES = {
|
|
25
|
-
sankey: 1, treemap: 1, boxplot: 1, network: 1, marimekko: 1, streamgraph: 1,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function pageBox() {
|
|
29
|
-
const p = LC.page;
|
|
30
|
-
return { pw: p.pw, ph: p.ph, mx: p.mx, cw: p.pw - 2 * p.mx };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** 内容区垂直范围(含注释层预留) */
|
|
34
|
-
function contentBand(opts) {
|
|
35
|
-
const L = LC.pageTypes.layout;
|
|
36
|
-
const C = LC.pageTypes.common;
|
|
37
|
-
const withNote = !!(opts && opts.withNote);
|
|
38
|
-
const top = (opts && opts.top != null) ? opts.top : C.bodyY;
|
|
39
|
-
const bottom = withNote ? L.contentBottomWithNote : L.contentBottom;
|
|
40
|
-
return { top, bottom, h: Math.max(0, bottom - top) };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 语义槽位 → {x,y,w,h}(英寸)
|
|
45
|
-
* 未识别页型/槽位时返回 null(调用方回落自身逻辑,不中断)。
|
|
46
|
-
*/
|
|
47
|
-
function regionOf(pageType, slotId, opts) {
|
|
48
|
-
const type = String(pageType || 'points');
|
|
49
|
-
const slot = String(slotId || '');
|
|
50
|
-
const { mx, cw } = pageBox();
|
|
51
|
-
const C = LC.pageTypes.common;
|
|
52
|
-
const L = LC.pageTypes.layout;
|
|
53
|
-
const PT = LC.pageTypes[type] || {};
|
|
54
|
-
|
|
55
|
-
// ── chrome:页头 ──
|
|
56
|
-
if (slot === 'head' || slot === 'chrome') {
|
|
57
|
-
return {
|
|
58
|
-
x: mx, y: C.headEyebrowY, w: cw,
|
|
59
|
-
h: Math.max(0.4, (C.headRuleY || 1.95) - C.headEyebrowY),
|
|
60
|
-
titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── annotation:so-what / 来源 / note ──
|
|
65
|
-
if (slot === 'annotation' || slot === 'soWhat') {
|
|
66
|
-
const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.soWhatY != null)
|
|
67
|
-
? LC.pageTypes.exhibit.soWhatY : 6.05;
|
|
68
|
-
return { x: mx, y, w: cw, h: 0.62 };
|
|
69
|
-
}
|
|
70
|
-
if (slot === 'footnote') {
|
|
71
|
-
const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.footnoteY != null)
|
|
72
|
-
? LC.pageTypes.exhibit.footnoteY
|
|
73
|
-
: ((LC.pageTypes.note && LC.pageTypes.note.y) || 6.55);
|
|
74
|
-
return { x: mx, y, w: cw - 1.2, h: (LC.pageTypes.note && LC.pageTypes.note.h) || 0.32 };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ── 页型主从区域 ──
|
|
78
|
-
if (type === 'points') {
|
|
79
|
-
const band = contentBand(opts);
|
|
80
|
-
if (slot === 'primary' || slot === 'list') {
|
|
81
|
-
return { x: mx, y: PT.listY || band.top, w: cw, h: Math.max(0.8, band.bottom - (PT.listY || band.top)) };
|
|
82
|
-
}
|
|
83
|
-
if (slot === 'metrics') {
|
|
84
|
-
return { x: mx, y: PT.metricY || 4.7, w: cw, h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)) };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (type === 'metrics') {
|
|
89
|
-
if (slot === 'primary') {
|
|
90
|
-
return {
|
|
91
|
-
x: mx, y: PT.bandY || 3.1, w: cw, h: PT.cardH || 2.3,
|
|
92
|
-
valY: PT.valY, valH: PT.valH, capY: PT.capY, capH: PT.capH,
|
|
93
|
-
gap: PT.gap, maxW: PT.maxW,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (type === 'table') {
|
|
99
|
-
if (slot === 'primary') {
|
|
100
|
-
const y = PT.y || L.contentTop;
|
|
101
|
-
return {
|
|
102
|
-
x: mx, y, w: cw,
|
|
103
|
-
h: Math.max(1, ((opts && opts.bottom) || L.contentBottom) - y),
|
|
104
|
-
rowH: PT.rowH, rowHMin: PT.rowHMin, headRowH: PT.headRowH,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (type === 'bar') {
|
|
110
|
-
if (slot === 'primary') {
|
|
111
|
-
const y = PT.chartY || 2.95;
|
|
112
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom
|
|
113
|
-
: ((opts && opts.withNote) ? L.contentBottomWithNote : L.contentBottom);
|
|
114
|
-
return { x: mx + (PT.chartX || 0), y, w: cw - (PT.chartW || 0), h: Math.max(1.2, bottom - y) };
|
|
115
|
-
}
|
|
116
|
-
if (slot === 'hbar') {
|
|
117
|
-
const y0 = PT.hbarY0 || 2.6;
|
|
118
|
-
const y1 = (opts && opts.bottom != null) ? opts.bottom : (PT.hbarY1 || 6.25);
|
|
119
|
-
return { x: mx, y: y0, w: cw, h: Math.max(1.2, y1 - y0) };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (type === 'donut') {
|
|
124
|
-
if (slot === 'primary') {
|
|
125
|
-
const y = PT.chartY || 2.95;
|
|
126
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
127
|
-
return { x: mx, y, w: cw, h: Math.max(1.2, bottom - y) };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (type === 'exhibit') {
|
|
132
|
-
if (slot === 'primary') {
|
|
133
|
-
const y = PT.chartY || 2.95;
|
|
134
|
-
const h = Math.max(1.2, (L.contentBottomWithNote - y));
|
|
135
|
-
return { x: mx, y, w: cw, h, badgeY: PT.badgeY };
|
|
136
|
-
}
|
|
137
|
-
if (slot === 'secondary') {
|
|
138
|
-
return { x: mx, y: PT.badgeY || L.contentTop, w: cw * 0.32, h: 0.4 };
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (type === 'points') {
|
|
143
|
-
const band = contentBand(opts);
|
|
144
|
-
if (slot === 'primary' || slot === 'list') {
|
|
145
|
-
return {
|
|
146
|
-
x: mx, y: PT.listY || band.top, w: cw,
|
|
147
|
-
h: Math.max(0.8, (opts && opts.bottom != null ? opts.bottom : band.bottom) - (PT.listY || band.top)),
|
|
148
|
-
rowH: PT.rowH, rowHMin: PT.rowHMin, markSize: PT.markSize,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
if (slot === 'metrics') {
|
|
152
|
-
return {
|
|
153
|
-
x: mx, y: PT.metricY || 4.7, w: cw,
|
|
154
|
-
h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)),
|
|
155
|
-
metricW: PT.metricW, metricValH: PT.metricValH, metricCapY: PT.metricCapY,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (type === 'split') {
|
|
161
|
-
const SP = PT;
|
|
162
|
-
const top = SP.chartY || L.contentTop;
|
|
163
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
164
|
-
if (slot === 'left') {
|
|
165
|
-
return {
|
|
166
|
-
x: mx, y: top, w: cw * (SP.leftW || 0.55),
|
|
167
|
-
h: Math.max(1.2, bottom - top),
|
|
168
|
-
tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
if (slot === 'right') {
|
|
172
|
-
const rx = mx + cw * (SP.rightX != null ? SP.rightX : 0.585);
|
|
173
|
-
return {
|
|
174
|
-
x: rx, y: top, w: cw * (SP.rightW || 0.415),
|
|
175
|
-
h: Math.max(1.2, bottom - top),
|
|
176
|
-
tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (type === 'twocol') {
|
|
182
|
-
const gap = PT.colGap || 0.5;
|
|
183
|
-
const colW = (cw - gap) / 2;
|
|
184
|
-
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
185
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
186
|
-
if (slot === 'primary' || slot === 'left') {
|
|
187
|
-
return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
|
|
188
|
-
}
|
|
189
|
-
if (slot === 'secondary' || slot === 'right') {
|
|
190
|
-
return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (type === 'diagram' || type === 'lane') {
|
|
195
|
-
if (slot === 'primary') {
|
|
196
|
-
const arch = LC.pageTypes.arch || {};
|
|
197
|
-
const y = (opts && opts.mode === 'architecture' && arch.fullStartY != null)
|
|
198
|
-
? arch.fullStartY
|
|
199
|
-
: (PT.bodyStartY || 2.9);
|
|
200
|
-
const b = (opts && opts.mode === 'architecture' && arch.fullEndY != null)
|
|
201
|
-
? arch.fullEndY
|
|
202
|
-
: ((opts && opts.bottom != null) ? opts.bottom : L.contentBottom);
|
|
203
|
-
return {
|
|
204
|
-
x: mx, y, w: cw, h: Math.max(1.2, b - y),
|
|
205
|
-
layerBarW: PT.layerBarW, layerGap: PT.layerGap, maxLayerH: PT.maxLayerH,
|
|
206
|
-
laneH: arch.laneH, laneHeadW: arch.laneHeadW, laneGap: arch.laneGap,
|
|
207
|
-
stepGap: arch.stepGap, stepMaxW: arch.stepMaxW,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ── 剩余高频页型 ──
|
|
213
|
-
if (type === 'kpi') {
|
|
214
|
-
if (slot === 'primary' || slot === 'hero') {
|
|
215
|
-
return {
|
|
216
|
-
x: mx, y: PT.heroY || 2.9, w: cw * (PT.heroW || 0.52), h: PT.heroH || 1.6,
|
|
217
|
-
dividerX: PT.dividerX, metricY0: PT.metricY0, metricRowH: PT.metricRowH,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
if (slot === 'metrics') {
|
|
221
|
-
const dx = PT.dividerX != null ? PT.dividerX : 0.56;
|
|
222
|
-
return {
|
|
223
|
-
x: mx + cw * dx + 0.35, y: PT.metricY0 || 2.95,
|
|
224
|
-
w: cw * (1 - dx) - 0.5, h: Math.max(0.6, L.contentBottom - (PT.metricY0 || 2.95)),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (type === 'comparison') {
|
|
230
|
-
const gap = PT.panelGap || 0.4;
|
|
231
|
-
const panelW = (cw - gap) / 2;
|
|
232
|
-
const y = PT.panelY || 2.55;
|
|
233
|
-
const h = PT.panelH || 3.4;
|
|
234
|
-
if (slot === 'primary' || slot === 'left') {
|
|
235
|
-
return { x: mx, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
|
|
236
|
-
}
|
|
237
|
-
if (slot === 'right' || slot === 'secondary') {
|
|
238
|
-
return { x: mx + panelW + gap, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (type === 'quote') {
|
|
243
|
-
if (slot === 'primary') {
|
|
244
|
-
return {
|
|
245
|
-
x: mx + 1.05, y: PT.textY || 2.7, w: cw - 2.1, h: PT.textH || 2.3,
|
|
246
|
-
markY: PT.markY, authorY: PT.authorY, contextY: PT.contextY,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (type === 'heatmap') {
|
|
252
|
-
if (slot === 'primary') {
|
|
253
|
-
const y = PT.y || 2.6;
|
|
254
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
255
|
-
return {
|
|
256
|
-
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
257
|
-
labelW: PT.labelW, headH: PT.headH, cellH: PT.cellH, cellGap: PT.cellGap,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (type === 'bullet') {
|
|
263
|
-
if (slot === 'primary') {
|
|
264
|
-
const y = PT.y || 2.6;
|
|
265
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
266
|
-
return {
|
|
267
|
-
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
268
|
-
rowH: PT.rowH, labelW: PT.labelW, valW: PT.valW, barH: PT.barH,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (type === 'pyramid') {
|
|
274
|
-
if (slot === 'primary') {
|
|
275
|
-
const y = PT.y || 2.35;
|
|
276
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
277
|
-
return {
|
|
278
|
-
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
279
|
-
rowH: PT.rowH, gap: PT.gap, minW: PT.minW, labelW: PT.labelW,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (type === 'steps') {
|
|
285
|
-
if (slot === 'primary') {
|
|
286
|
-
const y = PT.y || 3.05;
|
|
287
|
-
return {
|
|
288
|
-
x: mx, y, w: cw, h: PT.barH || 1.35,
|
|
289
|
-
gap: PT.gap, arrowW: PT.arrowW, numH: PT.numH, titleH: PT.titleH,
|
|
290
|
-
pad: PT.pad, maxPerRow: PT.maxPerRow,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (type === 'cards') {
|
|
296
|
-
if (slot === 'primary') {
|
|
297
|
-
const y = PT.startY || 3.0;
|
|
298
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
299
|
-
return { x: mx, y, w: cw, h: Math.max(1, bottom - y), gap: PT.gap, maxH: PT.maxH, titleH: PT.titleH };
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (type === 'timeline') {
|
|
304
|
-
if (slot === 'primary') {
|
|
305
|
-
return {
|
|
306
|
-
x: mx, y: PT.labelY || 2.95, w: cw,
|
|
307
|
-
h: Math.max(1, L.contentBottom - (PT.labelY || 2.95)),
|
|
308
|
-
axisY: PT.axisY, dotY: PT.dotY, dotR: PT.dotR, nameY: PT.nameY,
|
|
309
|
-
descY: PT.descY, descH: PT.descH,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (type === 'image') {
|
|
315
|
-
if (slot === 'primary') {
|
|
316
|
-
const y = Math.max(PT.y || 2.3, (opts && opts.top) || 0);
|
|
317
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
318
|
-
const capH = (opts && opts.caption) ? 0.32 : 0;
|
|
319
|
-
return {
|
|
320
|
-
x: mx, y, w: cw, h: Math.max(1.2, Math.min(PT.h || 4.2, bottom - y - capH)),
|
|
321
|
-
halfW: PT.halfW, halfGap: PT.halfGap, capY: PT.capY, capH: PT.capH || 0.3,
|
|
322
|
-
radius: PT.radius, gridGap: PT.gridGap, gridCols: PT.gridCols,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (type === 'halftable') {
|
|
328
|
-
if (slot === 'primary' || slot === 'left') {
|
|
329
|
-
const r = LC.pageTypes.research || {};
|
|
330
|
-
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
331
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
332
|
-
return { x: mx, y: top, w: cw * (r.halfTableW || 0.52), h: Math.max(1, bottom - top) };
|
|
333
|
-
}
|
|
334
|
-
if (slot === 'secondary' || slot === 'right') {
|
|
335
|
-
const r = LC.pageTypes.research || {};
|
|
336
|
-
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
337
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
338
|
-
return {
|
|
339
|
-
x: mx + cw * (r.halfChartX || 0.57), y: top,
|
|
340
|
-
w: cw * (r.halfChartW || 0.43), h: Math.max(1, bottom - top),
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (type === 'matrix') {
|
|
346
|
-
if (slot === 'primary') {
|
|
347
|
-
const r = LC.pageTypes.research || {};
|
|
348
|
-
const y = r.matrixY || 2.3;
|
|
349
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
350
|
-
return {
|
|
351
|
-
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
352
|
-
labelW: r.matrixLabelW, cellH: r.matrixCellH,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (type === 'threecol') {
|
|
358
|
-
const r = LC.pageTypes.research || {};
|
|
359
|
-
const gap = r.col3Gap || 0.38;
|
|
360
|
-
const colW = (cw - 2 * gap) / 3;
|
|
361
|
-
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
362
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
363
|
-
if (slot === 'primary' || slot === 'left') {
|
|
364
|
-
return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
365
|
-
}
|
|
366
|
-
if (slot === 'middle') {
|
|
367
|
-
return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
368
|
-
}
|
|
369
|
-
if (slot === 'right' || slot === 'secondary') {
|
|
370
|
-
return { x: mx + 2 * (colW + gap), y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// 信息图页型:内容区主图
|
|
375
|
-
if (INFO_TYPES[type] && (slot === 'primary')) {
|
|
376
|
-
const y = PT.y || L.contentTop;
|
|
377
|
-
const h = PT.h || 4.1;
|
|
378
|
-
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
379
|
-
return { x: mx, y, w: cw, h: Math.max(1.2, Math.min(h, bottom - y)) };
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// 默认:内容区全宽
|
|
383
|
-
if (slot === 'primary' || slot === 'secondary' || slot === 'left' || slot === 'right') {
|
|
384
|
-
const band = contentBand(opts);
|
|
385
|
-
return { x: mx, y: band.top, w: cw, h: band.h };
|
|
386
|
-
}
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** 槽位是否在 layout_slots.json 中声明为 required */
|
|
391
|
-
function slotRequired(pageType, slotId) {
|
|
392
|
-
const def = (SLOTS.pageTypes || {})[pageType];
|
|
393
|
-
if (!def) return false;
|
|
394
|
-
const s = (def.slots || []).find(x => x.id === slotId);
|
|
395
|
-
return !!(s && s.required);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/** 列出页型已声明槽位 id */
|
|
399
|
-
function slotIds(pageType) {
|
|
400
|
-
const def = (SLOTS.pageTypes || {})[pageType];
|
|
401
|
-
return (def && def.slots) ? def.slots.map(s => s.id) : [];
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
module.exports = {
|
|
405
|
-
LC,
|
|
406
|
-
SLOTS,
|
|
407
|
-
pageBox,
|
|
408
|
-
contentBand,
|
|
409
|
-
regionOf,
|
|
410
|
-
slotRequired,
|
|
411
|
-
slotIds,
|
|
412
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* TopPPT HTML · 页型布局区域 IR(双通道几何收敛起点)
|
|
3
|
+
*
|
|
4
|
+
* 从 layout-constants.json + layout_slots.json 派生「语义槽位 → 英寸矩形」。
|
|
5
|
+
* B 通道(build_pptx.js)直接 require;A 通道消费同一 LC 常量(数字同源),
|
|
6
|
+
* 本模块是把「槽位语义」从散落坐标收拢成可测接口的第一步。
|
|
7
|
+
*
|
|
8
|
+
* 纪律:改几何只改 layout-constants.json;改槽位只改 layout_slots.json。
|
|
9
|
+
* 新增页型先扩 layout_slots.json,再在此补 regionOf 分支,最后写双引擎。
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const ROOT = path.join(__dirname, '..');
|
|
17
|
+
const LC = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout-constants.json'), 'utf8'));
|
|
18
|
+
// 布局 IR:优先 layout-constants.layoutSlots(v9 单源合并);否则回落 layout_slots.json
|
|
19
|
+
let SLOTS = LC.layoutSlots;
|
|
20
|
+
if (!SLOTS || !SLOTS.pageTypes) {
|
|
21
|
+
SLOTS = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout_slots.json'), 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const INFO_TYPES = {
|
|
25
|
+
sankey: 1, treemap: 1, boxplot: 1, network: 1, marimekko: 1, streamgraph: 1,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function pageBox() {
|
|
29
|
+
const p = LC.page;
|
|
30
|
+
return { pw: p.pw, ph: p.ph, mx: p.mx, cw: p.pw - 2 * p.mx };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 内容区垂直范围(含注释层预留) */
|
|
34
|
+
function contentBand(opts) {
|
|
35
|
+
const L = LC.pageTypes.layout;
|
|
36
|
+
const C = LC.pageTypes.common;
|
|
37
|
+
const withNote = !!(opts && opts.withNote);
|
|
38
|
+
const top = (opts && opts.top != null) ? opts.top : C.bodyY;
|
|
39
|
+
const bottom = withNote ? L.contentBottomWithNote : L.contentBottom;
|
|
40
|
+
return { top, bottom, h: Math.max(0, bottom - top) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 语义槽位 → {x,y,w,h}(英寸)
|
|
45
|
+
* 未识别页型/槽位时返回 null(调用方回落自身逻辑,不中断)。
|
|
46
|
+
*/
|
|
47
|
+
function regionOf(pageType, slotId, opts) {
|
|
48
|
+
const type = String(pageType || 'points');
|
|
49
|
+
const slot = String(slotId || '');
|
|
50
|
+
const { mx, cw } = pageBox();
|
|
51
|
+
const C = LC.pageTypes.common;
|
|
52
|
+
const L = LC.pageTypes.layout;
|
|
53
|
+
const PT = LC.pageTypes[type] || {};
|
|
54
|
+
|
|
55
|
+
// ── chrome:页头 ──
|
|
56
|
+
if (slot === 'head' || slot === 'chrome') {
|
|
57
|
+
return {
|
|
58
|
+
x: mx, y: C.headEyebrowY, w: cw,
|
|
59
|
+
h: Math.max(0.4, (C.headRuleY || 1.95) - C.headEyebrowY),
|
|
60
|
+
titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── annotation:so-what / 来源 / note ──
|
|
65
|
+
if (slot === 'annotation' || slot === 'soWhat') {
|
|
66
|
+
const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.soWhatY != null)
|
|
67
|
+
? LC.pageTypes.exhibit.soWhatY : 6.05;
|
|
68
|
+
return { x: mx, y, w: cw, h: 0.62 };
|
|
69
|
+
}
|
|
70
|
+
if (slot === 'footnote') {
|
|
71
|
+
const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.footnoteY != null)
|
|
72
|
+
? LC.pageTypes.exhibit.footnoteY
|
|
73
|
+
: ((LC.pageTypes.note && LC.pageTypes.note.y) || 6.55);
|
|
74
|
+
return { x: mx, y, w: cw - 1.2, h: (LC.pageTypes.note && LC.pageTypes.note.h) || 0.32 };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── 页型主从区域 ──
|
|
78
|
+
if (type === 'points') {
|
|
79
|
+
const band = contentBand(opts);
|
|
80
|
+
if (slot === 'primary' || slot === 'list') {
|
|
81
|
+
return { x: mx, y: PT.listY || band.top, w: cw, h: Math.max(0.8, band.bottom - (PT.listY || band.top)) };
|
|
82
|
+
}
|
|
83
|
+
if (slot === 'metrics') {
|
|
84
|
+
return { x: mx, y: PT.metricY || 4.7, w: cw, h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (type === 'metrics') {
|
|
89
|
+
if (slot === 'primary') {
|
|
90
|
+
return {
|
|
91
|
+
x: mx, y: PT.bandY || 3.1, w: cw, h: PT.cardH || 2.3,
|
|
92
|
+
valY: PT.valY, valH: PT.valH, capY: PT.capY, capH: PT.capH,
|
|
93
|
+
gap: PT.gap, maxW: PT.maxW,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (type === 'table') {
|
|
99
|
+
if (slot === 'primary') {
|
|
100
|
+
const y = PT.y || L.contentTop;
|
|
101
|
+
return {
|
|
102
|
+
x: mx, y, w: cw,
|
|
103
|
+
h: Math.max(1, ((opts && opts.bottom) || L.contentBottom) - y),
|
|
104
|
+
rowH: PT.rowH, rowHMin: PT.rowHMin, headRowH: PT.headRowH,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (type === 'bar') {
|
|
110
|
+
if (slot === 'primary') {
|
|
111
|
+
const y = PT.chartY || 2.95;
|
|
112
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom
|
|
113
|
+
: ((opts && opts.withNote) ? L.contentBottomWithNote : L.contentBottom);
|
|
114
|
+
return { x: mx + (PT.chartX || 0), y, w: cw - (PT.chartW || 0), h: Math.max(1.2, bottom - y) };
|
|
115
|
+
}
|
|
116
|
+
if (slot === 'hbar') {
|
|
117
|
+
const y0 = PT.hbarY0 || 2.6;
|
|
118
|
+
const y1 = (opts && opts.bottom != null) ? opts.bottom : (PT.hbarY1 || 6.25);
|
|
119
|
+
return { x: mx, y: y0, w: cw, h: Math.max(1.2, y1 - y0) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (type === 'donut') {
|
|
124
|
+
if (slot === 'primary') {
|
|
125
|
+
const y = PT.chartY || 2.95;
|
|
126
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
127
|
+
return { x: mx, y, w: cw, h: Math.max(1.2, bottom - y) };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (type === 'exhibit') {
|
|
132
|
+
if (slot === 'primary') {
|
|
133
|
+
const y = PT.chartY || 2.95;
|
|
134
|
+
const h = Math.max(1.2, (L.contentBottomWithNote - y));
|
|
135
|
+
return { x: mx, y, w: cw, h, badgeY: PT.badgeY };
|
|
136
|
+
}
|
|
137
|
+
if (slot === 'secondary') {
|
|
138
|
+
return { x: mx, y: PT.badgeY || L.contentTop, w: cw * 0.32, h: 0.4 };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (type === 'points') {
|
|
143
|
+
const band = contentBand(opts);
|
|
144
|
+
if (slot === 'primary' || slot === 'list') {
|
|
145
|
+
return {
|
|
146
|
+
x: mx, y: PT.listY || band.top, w: cw,
|
|
147
|
+
h: Math.max(0.8, (opts && opts.bottom != null ? opts.bottom : band.bottom) - (PT.listY || band.top)),
|
|
148
|
+
rowH: PT.rowH, rowHMin: PT.rowHMin, markSize: PT.markSize,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (slot === 'metrics') {
|
|
152
|
+
return {
|
|
153
|
+
x: mx, y: PT.metricY || 4.7, w: cw,
|
|
154
|
+
h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)),
|
|
155
|
+
metricW: PT.metricW, metricValH: PT.metricValH, metricCapY: PT.metricCapY,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (type === 'split') {
|
|
161
|
+
const SP = PT;
|
|
162
|
+
const top = SP.chartY || L.contentTop;
|
|
163
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
164
|
+
if (slot === 'left') {
|
|
165
|
+
return {
|
|
166
|
+
x: mx, y: top, w: cw * (SP.leftW || 0.55),
|
|
167
|
+
h: Math.max(1.2, bottom - top),
|
|
168
|
+
tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (slot === 'right') {
|
|
172
|
+
const rx = mx + cw * (SP.rightX != null ? SP.rightX : 0.585);
|
|
173
|
+
return {
|
|
174
|
+
x: rx, y: top, w: cw * (SP.rightW || 0.415),
|
|
175
|
+
h: Math.max(1.2, bottom - top),
|
|
176
|
+
tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (type === 'twocol') {
|
|
182
|
+
const gap = PT.colGap || 0.5;
|
|
183
|
+
const colW = (cw - gap) / 2;
|
|
184
|
+
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
185
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
186
|
+
if (slot === 'primary' || slot === 'left') {
|
|
187
|
+
return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
|
|
188
|
+
}
|
|
189
|
+
if (slot === 'secondary' || slot === 'right') {
|
|
190
|
+
return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (type === 'diagram' || type === 'lane') {
|
|
195
|
+
if (slot === 'primary') {
|
|
196
|
+
const arch = LC.pageTypes.arch || {};
|
|
197
|
+
const y = (opts && opts.mode === 'architecture' && arch.fullStartY != null)
|
|
198
|
+
? arch.fullStartY
|
|
199
|
+
: (PT.bodyStartY || 2.9);
|
|
200
|
+
const b = (opts && opts.mode === 'architecture' && arch.fullEndY != null)
|
|
201
|
+
? arch.fullEndY
|
|
202
|
+
: ((opts && opts.bottom != null) ? opts.bottom : L.contentBottom);
|
|
203
|
+
return {
|
|
204
|
+
x: mx, y, w: cw, h: Math.max(1.2, b - y),
|
|
205
|
+
layerBarW: PT.layerBarW, layerGap: PT.layerGap, maxLayerH: PT.maxLayerH,
|
|
206
|
+
laneH: arch.laneH, laneHeadW: arch.laneHeadW, laneGap: arch.laneGap,
|
|
207
|
+
stepGap: arch.stepGap, stepMaxW: arch.stepMaxW,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── 剩余高频页型 ──
|
|
213
|
+
if (type === 'kpi') {
|
|
214
|
+
if (slot === 'primary' || slot === 'hero') {
|
|
215
|
+
return {
|
|
216
|
+
x: mx, y: PT.heroY || 2.9, w: cw * (PT.heroW || 0.52), h: PT.heroH || 1.6,
|
|
217
|
+
dividerX: PT.dividerX, metricY0: PT.metricY0, metricRowH: PT.metricRowH,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (slot === 'metrics') {
|
|
221
|
+
const dx = PT.dividerX != null ? PT.dividerX : 0.56;
|
|
222
|
+
return {
|
|
223
|
+
x: mx + cw * dx + 0.35, y: PT.metricY0 || 2.95,
|
|
224
|
+
w: cw * (1 - dx) - 0.5, h: Math.max(0.6, L.contentBottom - (PT.metricY0 || 2.95)),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (type === 'comparison') {
|
|
230
|
+
const gap = PT.panelGap || 0.4;
|
|
231
|
+
const panelW = (cw - gap) / 2;
|
|
232
|
+
const y = PT.panelY || 2.55;
|
|
233
|
+
const h = PT.panelH || 3.4;
|
|
234
|
+
if (slot === 'primary' || slot === 'left') {
|
|
235
|
+
return { x: mx, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
|
|
236
|
+
}
|
|
237
|
+
if (slot === 'right' || slot === 'secondary') {
|
|
238
|
+
return { x: mx + panelW + gap, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (type === 'quote') {
|
|
243
|
+
if (slot === 'primary') {
|
|
244
|
+
return {
|
|
245
|
+
x: mx + 1.05, y: PT.textY || 2.7, w: cw - 2.1, h: PT.textH || 2.3,
|
|
246
|
+
markY: PT.markY, authorY: PT.authorY, contextY: PT.contextY,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (type === 'heatmap') {
|
|
252
|
+
if (slot === 'primary') {
|
|
253
|
+
const y = PT.y || 2.6;
|
|
254
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
255
|
+
return {
|
|
256
|
+
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
257
|
+
labelW: PT.labelW, headH: PT.headH, cellH: PT.cellH, cellGap: PT.cellGap,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (type === 'bullet') {
|
|
263
|
+
if (slot === 'primary') {
|
|
264
|
+
const y = PT.y || 2.6;
|
|
265
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
266
|
+
return {
|
|
267
|
+
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
268
|
+
rowH: PT.rowH, labelW: PT.labelW, valW: PT.valW, barH: PT.barH,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (type === 'pyramid') {
|
|
274
|
+
if (slot === 'primary') {
|
|
275
|
+
const y = PT.y || 2.35;
|
|
276
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
277
|
+
return {
|
|
278
|
+
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
279
|
+
rowH: PT.rowH, gap: PT.gap, minW: PT.minW, labelW: PT.labelW,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (type === 'steps') {
|
|
285
|
+
if (slot === 'primary') {
|
|
286
|
+
const y = PT.y || 3.05;
|
|
287
|
+
return {
|
|
288
|
+
x: mx, y, w: cw, h: PT.barH || 1.35,
|
|
289
|
+
gap: PT.gap, arrowW: PT.arrowW, numH: PT.numH, titleH: PT.titleH,
|
|
290
|
+
pad: PT.pad, maxPerRow: PT.maxPerRow,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (type === 'cards') {
|
|
296
|
+
if (slot === 'primary') {
|
|
297
|
+
const y = PT.startY || 3.0;
|
|
298
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
299
|
+
return { x: mx, y, w: cw, h: Math.max(1, bottom - y), gap: PT.gap, maxH: PT.maxH, titleH: PT.titleH };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (type === 'timeline') {
|
|
304
|
+
if (slot === 'primary') {
|
|
305
|
+
return {
|
|
306
|
+
x: mx, y: PT.labelY || 2.95, w: cw,
|
|
307
|
+
h: Math.max(1, L.contentBottom - (PT.labelY || 2.95)),
|
|
308
|
+
axisY: PT.axisY, dotY: PT.dotY, dotR: PT.dotR, nameY: PT.nameY,
|
|
309
|
+
descY: PT.descY, descH: PT.descH,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (type === 'image') {
|
|
315
|
+
if (slot === 'primary') {
|
|
316
|
+
const y = Math.max(PT.y || 2.3, (opts && opts.top) || 0);
|
|
317
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
318
|
+
const capH = (opts && opts.caption) ? 0.32 : 0;
|
|
319
|
+
return {
|
|
320
|
+
x: mx, y, w: cw, h: Math.max(1.2, Math.min(PT.h || 4.2, bottom - y - capH)),
|
|
321
|
+
halfW: PT.halfW, halfGap: PT.halfGap, capY: PT.capY, capH: PT.capH || 0.3,
|
|
322
|
+
radius: PT.radius, gridGap: PT.gridGap, gridCols: PT.gridCols,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (type === 'halftable') {
|
|
328
|
+
if (slot === 'primary' || slot === 'left') {
|
|
329
|
+
const r = LC.pageTypes.research || {};
|
|
330
|
+
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
331
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
332
|
+
return { x: mx, y: top, w: cw * (r.halfTableW || 0.52), h: Math.max(1, bottom - top) };
|
|
333
|
+
}
|
|
334
|
+
if (slot === 'secondary' || slot === 'right') {
|
|
335
|
+
const r = LC.pageTypes.research || {};
|
|
336
|
+
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
337
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
338
|
+
return {
|
|
339
|
+
x: mx + cw * (r.halfChartX || 0.57), y: top,
|
|
340
|
+
w: cw * (r.halfChartW || 0.43), h: Math.max(1, bottom - top),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (type === 'matrix') {
|
|
346
|
+
if (slot === 'primary') {
|
|
347
|
+
const r = LC.pageTypes.research || {};
|
|
348
|
+
const y = r.matrixY || 2.3;
|
|
349
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
350
|
+
return {
|
|
351
|
+
x: mx, y, w: cw, h: Math.max(1, bottom - y),
|
|
352
|
+
labelW: r.matrixLabelW, cellH: r.matrixCellH,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (type === 'threecol') {
|
|
358
|
+
const r = LC.pageTypes.research || {};
|
|
359
|
+
const gap = r.col3Gap || 0.38;
|
|
360
|
+
const colW = (cw - 2 * gap) / 3;
|
|
361
|
+
const top = (opts && opts.top != null) ? opts.top : L.contentTop;
|
|
362
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
363
|
+
if (slot === 'primary' || slot === 'left') {
|
|
364
|
+
return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
365
|
+
}
|
|
366
|
+
if (slot === 'middle') {
|
|
367
|
+
return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
368
|
+
}
|
|
369
|
+
if (slot === 'right' || slot === 'secondary') {
|
|
370
|
+
return { x: mx + 2 * (colW + gap), y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 信息图页型:内容区主图
|
|
375
|
+
if (INFO_TYPES[type] && (slot === 'primary')) {
|
|
376
|
+
const y = PT.y || L.contentTop;
|
|
377
|
+
const h = PT.h || 4.1;
|
|
378
|
+
const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
|
|
379
|
+
return { x: mx, y, w: cw, h: Math.max(1.2, Math.min(h, bottom - y)) };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 默认:内容区全宽
|
|
383
|
+
if (slot === 'primary' || slot === 'secondary' || slot === 'left' || slot === 'right') {
|
|
384
|
+
const band = contentBand(opts);
|
|
385
|
+
return { x: mx, y: band.top, w: cw, h: band.h };
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** 槽位是否在 layout_slots.json 中声明为 required */
|
|
391
|
+
function slotRequired(pageType, slotId) {
|
|
392
|
+
const def = (SLOTS.pageTypes || {})[pageType];
|
|
393
|
+
if (!def) return false;
|
|
394
|
+
const s = (def.slots || []).find(x => x.id === slotId);
|
|
395
|
+
return !!(s && s.required);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** 列出页型已声明槽位 id */
|
|
399
|
+
function slotIds(pageType) {
|
|
400
|
+
const def = (SLOTS.pageTypes || {})[pageType];
|
|
401
|
+
return (def && def.slots) ? def.slots.map(s => s.id) : [];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
module.exports = {
|
|
405
|
+
LC,
|
|
406
|
+
SLOTS,
|
|
407
|
+
pageBox,
|
|
408
|
+
contentBand,
|
|
409
|
+
regionOf,
|
|
410
|
+
slotRequired,
|
|
411
|
+
slotIds,
|
|
412
|
+
};
|