@topmindspace/tms-skills 0.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 +26 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/bin/tms-skills.js +160 -0
- package/package.json +48 -0
- package/top-ppt-html/README.md +254 -0
- package/top-ppt-html/SKILL.md +111 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +168 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -0
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +168 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -0
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +321 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +914 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -0
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +914 -0
- package/top-ppt-html/assets/pptx-export.js +1944 -0
- package/top-ppt-html/assets/style-gallery.html +589 -0
- package/top-ppt-html/assets/templates/architecture.html +3728 -0
- package/top-ppt-html/assets/templates/engine.css +840 -0
- package/top-ppt-html/assets/templates/presentation.html +3738 -0
- package/top-ppt-html/assets/templates/research.html +4017 -0
- package/top-ppt-html/assets/templates/ui.js +520 -0
- 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 -0
- package/top-ppt-html/evals/rubric.schema.json +25 -0
- package/top-ppt-html/evals/run_evals.py +220 -0
- package/top-ppt-html/evals/trace.example.json +16 -0
- package/top-ppt-html/package.json +35 -0
- package/top-ppt-html/references/charts-basic.md +624 -0
- package/top-ppt-html/references/charts-discipline.md +108 -0
- package/top-ppt-html/references/charts-extended.md +482 -0
- package/top-ppt-html/references/charts.md +28 -0
- package/top-ppt-html/references/components-atoms.md +624 -0
- package/top-ppt-html/references/components.md +30 -0
- package/top-ppt-html/references/content-rules.md +490 -0
- package/top-ppt-html/references/design-system-engine.md +235 -0
- package/top-ppt-html/references/design-system.md +478 -0
- package/top-ppt-html/references/failure-modes.md +214 -0
- package/top-ppt-html/references/high-fidelity.md +127 -0
- package/top-ppt-html/references/icons.md +397 -0
- package/top-ppt-html/references/industry-benchmark.md +105 -0
- package/top-ppt-html/references/infographics-stats.md +308 -0
- package/top-ppt-html/references/infographics-structure.md +226 -0
- package/top-ppt-html/references/infographics.md +43 -0
- package/top-ppt-html/references/layout-grammar.md +315 -0
- package/top-ppt-html/references/layouts-architecture.md +108 -0
- package/top-ppt-html/references/layouts-combo.md +600 -0
- package/top-ppt-html/references/layouts-research.md +160 -0
- package/top-ppt-html/references/modes.md +254 -0
- package/top-ppt-html/references/outline-design.md +275 -0
- package/top-ppt-html/references/playbook.md +266 -0
- package/top-ppt-html/references/pptx-export.md +209 -0
- package/top-ppt-html/references/reform-plan.md +252 -0
- package/top-ppt-html/references/styles.md +370 -0
- package/top-ppt-html/references/tech-design.md +138 -0
- package/top-ppt-html/scripts/audit_css.py +109 -0
- package/top-ppt-html/scripts/audit_docs.py +176 -0
- package/top-ppt-html/scripts/audit_skill.py +220 -0
- package/top-ppt-html/scripts/audit_styles.py +351 -0
- package/top-ppt-html/scripts/build_examples.py +2276 -0
- package/top-ppt-html/scripts/build_pptx.js +2380 -0
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -0
- package/top-ppt-html/scripts/checks_html.py +127 -0
- package/top-ppt-html/scripts/cross_verify.py +294 -0
- package/top-ppt-html/scripts/env_probe.py +158 -0
- package/top-ppt-html/scripts/extract_model.py +210 -0
- package/top-ppt-html/scripts/extract_snippet.py +374 -0
- package/top-ppt-html/scripts/gen_channel_a.js +214 -0
- package/top-ppt-html/scripts/layout-constants.json +3377 -0
- package/top-ppt-html/scripts/layout_slots.json +830 -0
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -0
- package/top-ppt-html/scripts/measure_height.py +178 -0
- package/top-ppt-html/scripts/model-schema.json +547 -0
- package/top-ppt-html/scripts/negative_tests.py +307 -0
- package/top-ppt-html/scripts/package_skill.py +291 -0
- package/top-ppt-html/scripts/prepare_images.py +341 -0
- package/top-ppt-html/scripts/probe_image_export.py +188 -0
- package/top-ppt-html/scripts/quality_gate.py +301 -0
- package/top-ppt-html/scripts/regression.py +308 -0
- package/top-ppt-html/scripts/render_compare.py +275 -0
- package/top-ppt-html/scripts/render_from_model.py +698 -0
- package/top-ppt-html/scripts/scaffold_report.py +1054 -0
- package/top-ppt-html/scripts/section-file-map.json +105 -0
- package/top-ppt-html/scripts/sync_runtime.py +662 -0
- package/top-ppt-html/scripts/validate_pptx.py +1510 -0
- package/top-ppt-html/scripts/validate_report.py +1456 -0
|
@@ -0,0 +1,1944 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
* TopPPT HTML· 浏览器端 PPTX 预览运行时
|
|
3
|
+
* 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
|
|
4
|
+
*
|
|
5
|
+
* 定位:HTML 是阅读与演示界面,PPTX 是协作与修改格式——两者来自同一内容模型
|
|
6
|
+
* (window.REPORT_MODEL)。页面只提供「预览 PPTX」(WYSIWYG 模态,与智能体精导通道
|
|
7
|
+
* 同一序列化引擎)与可复制 AI 提示词;正式 PPTX 一律由本地智能体走精导通道
|
|
8
|
+
* (build_pptx.js)生成并过 strict 硬门禁,浏览器端导出达不到该质量标准。
|
|
9
|
+
*
|
|
10
|
+
* API:
|
|
11
|
+
* TopPptHtml.slidesXml(model) → string[](每页 slide XML,预览模态渲染用)
|
|
12
|
+
* TopPptHtml.validateModel(model) → {ok, missing[], warnings[], pages}(schema 单源驱动)
|
|
13
|
+
* TopPptHtml.slidesOf / slideXml / relsXml / NS / PRESETS
|
|
14
|
+
* → 供 scripts/gen_channel_a.js 在 Node 侧组装完整
|
|
15
|
+
* PPTX 包(回归双裁判内部质量路径,页面不使用)
|
|
16
|
+
*
|
|
17
|
+
* model = window.REPORT_MODEL(JSON,与页面正文同源):
|
|
18
|
+
* { mode, style, theme(light|dark:PPTX 按此选亮/暗 token,默认 light),
|
|
19
|
+
* title, subtitle, meta, agenda:[[num,标题,说明]…](architecture 可省),
|
|
20
|
+
* sections:[{type: points|metrics|kpi|table|timeline|steps|bar|donut|heatmap|bullet|pyramid|
|
|
21
|
+
* image|cards|split|comparison|quote|diagram|exhibit|twocol|threecol|halftable|
|
|
22
|
+
* matrix|lane, soWhat?, footnote?, flags?, image?, …}],
|
|
23
|
+
* closing:{title,points} }
|
|
24
|
+
* 通用可选字段:soWhat(结论条)/ footnote(来源行)/ flags(待核实清单 → accent 强调色条)
|
|
25
|
+
* / image(素材图片或配图占位,{src|items|placeholder, layout, fit, caption, alt, hint},
|
|
26
|
+
* src 仅 data: 或相对路径;layout ∈ full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec)。
|
|
27
|
+
* 页型字段定义(29 种)与 chart.type 白名单(30 类)的唯一事实源:scripts/model-schema.json。
|
|
28
|
+
*
|
|
29
|
+
* ★ 单源机制(scripts/sync_runtime.py 注入,标记块内禁止手改):
|
|
30
|
+
* - 版式常量:scripts/layout-constants.json → __TOPPPT_CONSTANTS__ 块;
|
|
31
|
+
* 通道 B(build_pptx.js)require 同一份 JSON,两通道产出一致。
|
|
32
|
+
* - DSL schema:scripts/model-schema.json → __TOPPPT_SCHEMA__ 块;
|
|
33
|
+
* scripts/extract_model.py 读同一份文件,双端校验同源。
|
|
34
|
+
* ★ 三模式独立排版比例尺(MTS)——research 咨询密排 / architecture 全幅图页型;
|
|
35
|
+
* modeSize() 把调用点的 presentation 基准 pt 映射到当前模式比例尺。
|
|
36
|
+
* ★ 本运行时只做「预览与回归裁判」:图表为形状近似渲染(交付通道 B 用原生数据图表,
|
|
37
|
+
* 可在 PowerPoint 中"编辑数据");文本与版式几何两通道同源,cross_verify 逐页比对。
|
|
38
|
+
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
39
|
+
(function (g) {
|
|
40
|
+
'use strict';
|
|
41
|
+
/* 双形态载荷判定:[[t,d]…] 数组 vs [{t,d,accent}…] 记录。typeof [] === 'object'。 */
|
|
42
|
+
function isRec(v) { return !!v && typeof v === 'object' && !Array.isArray(v); }
|
|
43
|
+
|
|
44
|
+
/* __TOPPPT_CONSTANTS_START__ */
|
|
45
|
+
/* ── 由 scripts/sync_runtime.py 从 scripts/layout-constants.json 注入 · 禁止手改 ── */
|
|
46
|
+
/* MTS = 三模式独立排版比例尺(按模式取 pt;基准 TS_BASE = presentation 档) */
|
|
47
|
+
/* PRESETS_DARK = 风格 dark token(REPORT_MODEL.theme="dark" 时导出深色版)*/
|
|
48
|
+
/* GRID = 12 列网格;TYPO = 语义字阶 C0–T14;CONT = 容器内边距;REG = 图表登记四元组;DEEP = 深度模式配置 */
|
|
49
|
+
/* IMG = 素材图片规格与配图占位约定(版式/裁切/建议尺寸/占位标签,双引擎同源) */
|
|
50
|
+
/* STYLE_DATA_COLORS(_DARK) = 9 套风格各自的编码色板 c1–c5(数据系列;结构色纪律不变) */
|
|
51
|
+
/* LAYOUT_SLOTS + regionOf = 页型布局区域 IR(与 scripts/lib_layout_regions.js 同源) */
|
|
52
|
+
var PW = 13.333, PH = 7.5, MX = 0.6, CW = PW - 2 * MX, EMU = 914400;
|
|
53
|
+
var TS_BASE = {"coverTitle":44,"coverSub":18,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10};
|
|
54
|
+
var MTS = {"presentation":{"coverTitle":44,"coverSub":18,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10},"research":{"coverTitle":36,"coverSub":14,"h1":22,"h2":13.5,"lead":11.5,"body":10.5,"caption":9,"micro":8.5},"architecture":{"coverTitle":44,"coverSub":19,"h1":30,"h2":17,"lead":14,"body":13,"caption":11,"micro":10}};
|
|
55
|
+
var PT = {"layout":{"contentTop":2.35,"contentBottom":6.9,"contentBottomWithNote":6.4,"pageNumY":7.0},"common":{"headEyebrowY":0.48,"headTitleY":0.82,"headRuleY":1.72,"leadY":1.88,"bodyY":2.35,"bodyYWithLead":2.55,"pagerY":7.0},"cover":{"metaY":0.7,"titleY":2.4,"titleH":2.0,"subtitleY":4.5,"subtitleH":0.8},"closing":{"eyebrowY":0.7,"titleY":1.2,"titleH":1.2,"pointsY":3.0,"pointTitleH":0.6,"pointBodyH":1.2},"points":{"listY":2.5,"rowH":0.72,"rowHMin":0.5,"markSize":0.14,"metricY":4.7,"metricW":2.6,"metricValH":0.9,"metricCapY":5.6},"metrics":{"bandY":3.1,"cardH":2.3,"valY":3.45,"valH":1.1,"capY":4.55,"capH":0.7,"gap":0.3,"maxW":2.9},"table":{"y":2.5,"rowH":0.55,"rowHMin":0.32,"headRowH":0.5},"bar":{"hbarY0":2.6,"hbarY1":6.25,"chartX":0.6,"chartY":2.95,"chartW":1.2,"chartH":3.35,"noteY":6.55},"note":{"y":6.55,"h":0.4},"exhibit":{"badgeY":2.35,"chartY":2.75,"chartH":3.25,"soWhatY":6.05,"footnoteY":6.72},"twocol":{"colGap":0.5,"bodyH":3.6,"rowHMin":0.5},"hbar":{"labelW":1.9,"valW":1.0,"rowH":0.55,"barRatio":0.5},"diagram":{"layerBarW":1.35,"layerGap":0.15,"nodeGap":0.15,"bodyStartY":2.9,"maxLayerH":1.15,"nodeMaxW":2.2},"split":{"leftW":0.55,"rightW":0.415,"rightX":0.585,"pointsY":2.7,"rowH":0.78,"capY":2.55,"chartY":3.0,"chartH":3.7,"tableY":2.7,"tableRowH":0.5},"cards":{"startY":3.0,"gap":0.25,"maxH":2.0,"titleH":0.4},"timeline":{"labelY":2.95,"axisY":3.58,"dotY":3.51,"dotR":0.18,"nameY":3.9,"descY":4.45,"descH":1.2},"research":{"col3Gap":0.38,"matrixLabelW":1.5,"matrixCellH":0.85,"matrixY":2.2,"denseTableY":2.2,"denseRowH":0.42,"halfTableW":0.52,"halfChartX":0.57,"halfChartW":0.43},"arch":{"fullStartY":2.1,"fullEndY":6.9,"laneH":0.72,"laneHMin":0.5,"laneGap":0.14,"laneHeadW":1.3,"stepGap":0.12,"stepMaxW":1.9},"kpi":{"heroY":2.9,"heroW":0.52,"heroH":1.6,"dividerX":0.56,"metricY0":2.95,"metricRowH":0.85},"comparison":{"panelGap":0.4,"panelY":2.55,"panelH":3.4,"titleH":0.45,"rowH":0.62},"quote":{"markY":1.75,"textY":2.7,"textH":2.3,"authorY":5.25,"contextY":5.8},"donut":{"centerX":3.05,"centerY":4.6,"r":1.6,"holeR":0.95,"legendX":5.6,"legendRowH":0.55,"legendLabelW":3.4,"legendValW":1.3,"legendPctX":5.3,"legendPctW":1.3},"steps":{"y":3.05,"barH":1.35,"gap":0.12,"arrowW":0.3,"numH":0.3,"titleH":0.34,"pad":0.08,"maxPerRow":6},"heatmap":{"y":2.6,"labelW":1.7,"headH":0.42,"cellH":0.6,"cellGap":0.06,"legendW":1.6},"pyramid":{"y":2.35,"rowH":0.72,"gap":0.08,"minW":0.34,"labelW":3.2},"bullet":{"y":2.6,"rowH":0.8,"labelW":2.4,"valW":1.7,"barH":0.26},"image":{"y":2.3,"h":4.2,"halfW":0.56,"halfGap":0.06,"capY":6.6,"capH":0.3,"radius":0.09,"noteRowH":0.62,"gridGap":0.16,"gridCols":{"2":2,"3":3,"4":4,"6":3},"gridCapH":0.28,"compareGap":0.3,"compareTagH":0.26,"wallCellH":0.92,"wallGap":0.18,"wallMaxRows":2},"flagbar":{"hdH":0.26,"rowH":0.24,"maxRows":3,"gap":0.12},"sankey":{"y":2.45,"h":4.1,"nodeW":0.32,"nodeGap":0.14,"labelW":1.55,"minBandH":0.22,"maxNodes":12,"maxFlows":24},"treemap":{"y":2.45,"h":4.1,"gap":0.05,"labelMinH":0.34,"maxLeaves":16,"maxDepth":3},"boxplot":{"y":2.7,"h":3.7,"labelW":1.5,"groupGap":0.3,"boxMaxW":0.9,"whiskerCapW":0.34,"maxGroups":8},"network":{"y":2.4,"h":4.2,"nodeR":0.28,"nodeRMin":0.18,"labelMaxW":1.4,"maxNodes":18,"maxEdges":30},"marimekko":{"y":2.6,"h":3.75,"labelW":1.55,"colGap":0.06,"legendH":0.42,"maxCols":6,"maxSegs":4},"streamgraph":{"y":2.6,"h":3.75,"labelW":1.4,"bandMinH":0.12,"maxSeries":6}};
|
|
56
|
+
var PRESETS = {
|
|
57
|
+
'business-blue': { accent:'1A73E8', ink:'202124', body:'5F6368', faint:'80868B', bg:'FFFFFF', surface:'F8F9FA', line:'DADCE0', soft:'E8F0FE', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
58
|
+
'apple-mono': { accent:'1D1D1F', ink:'1D1D1F', body:'6E6E73', faint:'86868B', bg:'FFFFFF', surface:'F5F5F7', line:'D2D2D7', soft:'F5F5F7', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
59
|
+
'mckinsey': { accent:'003A70', ink:'101828', body:'475467', faint:'667085', bg:'FFFFFF', surface:'F2F4F7', line:'D0D5DD', soft:'EEF4FB', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
60
|
+
'brand-red': { accent:'D0021B', ink:'1F1A1B', body:'5D5456', faint:'8A8083', bg:'FFFFFF', surface:'FAF7F7', line:'E2D9DA', soft:'FBECEC', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
61
|
+
'warm-sand': { accent:'96681F', ink:'2A2521', body:'6B5F52', faint:'93836F', bg:'FBF9F6', surface:'F6F2EC', line:'E0D7C9', soft:'F3ECDD', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
62
|
+
'deep-teal': { accent:'0F6B5C', ink:'14201E', body:'4A5A57', faint:'73837F', bg:'FFFFFF', surface:'F2F6F5', line:'CFDEDC', soft:'E4F1EE', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
63
|
+
'graphite-dark': { accent:'0369A1', ink:'1A1D21', body:'4B5158', faint:'767C84', bg:'F4F5F7', surface:'ECEEF1', line:'CED1D6', soft:'E2EFF7', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
64
|
+
'indigo-violet': { accent:'5B5BD6', ink:'1B1A2E', body:'52506B', faint:'7A779A', bg:'FFFFFF', surface:'F4F4FB', line:'D7D7E8', soft:'ECECF9', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
65
|
+
'spectrum': { accent:'4563EF', ink:'171A21', body:'4D5566', faint:'7B8494', bg:'FBFCFE', surface:'F4F6FA', line:'D6DCE6', soft:'ECEEFF', onAccent:'FFFFFF', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' }
|
|
66
|
+
};
|
|
67
|
+
var PRESETS_DARK = {
|
|
68
|
+
'business-blue': { accent:'8AB4F8', ink:'E8EAED', body:'ADB4BD', faint:'868D96', bg:'0D0F13', surface:'191D23', line:'2F353D', soft:'16243A', onAccent:'0D1B2E', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
69
|
+
'apple-mono': { accent:'F5F5F7', ink:'F5F5F7', body:'A1A1A6', faint:'86868B', bg:'000000', surface:'161617', line:'38383D', soft:'1D1D1F', onAccent:'000000', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
70
|
+
'mckinsey': { accent:'7FB2E5', ink:'E4E7EC', body:'AAB3C2', faint:'8A94A6', bg:'0C1116', surface:'12181F', line:'2A3342', soft:'12233A', onAccent:'0C1A2E', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
71
|
+
'brand-red': { accent:'FF5A66', ink:'F3ECEC', body:'C4B6B7', faint:'9A8D8E', bg:'130D0E', surface:'1A1213', line:'37272A', soft:'331518', onAccent:'2A0D10', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
72
|
+
'warm-sand': { accent:'D9B36A', ink:'ECE5DB', body:'C4B8A8', faint:'9B8D7A', bg:'15120E', surface:'1D1913', line:'3A3128', soft:'2E2417', onAccent:'2A1F10', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
73
|
+
'deep-teal': { accent:'4FC3B0', ink:'E2ECEA', body:'B3C2BF', faint:'86948F', bg:'0D1312', surface:'131B1A', line:'293834', soft:'12312C', onAccent:'0C2420', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
74
|
+
'graphite-dark': { accent:'4DA3FF', ink:'EEF2F6', body:'B9C2CD', faint:'85909D', bg:'0B0E12', surface:'171D24', line:'2B3540', soft:'132841', onAccent:'07182B', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
75
|
+
'indigo-violet': { accent:'A5A4F0', ink:'E6E5F5', body:'B6B4D4', faint:'8B89B0', bg:'100F1C', surface:'171629', line:'322F52', soft:'232146', onAccent:'171631', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' },
|
|
76
|
+
'spectrum': { accent:'8BA0F8', ink:'E8EBF2', body:'A7AFBF', faint:'7E8798', bg:'0E1116', surface:'171C26', line:'2B3342', soft:'1B2242', onAccent:'141A38', font:'Microsoft YaHei', fontDisplay:'Microsoft YaHei' }
|
|
77
|
+
};
|
|
78
|
+
var STYLE_DATA_COLORS = {"business-blue":["1a73e8","3526bb","21a199","b77148","99983d"],"apple-mono":["1d1d1f","0071e3","0f7b6c","c2410c","7c3aed"],"mckinsey":["003a70","2826bb","21a18e","b76848","99903d"],"brand-red":["d0021b","bb6326","d634ae","40a182","438aa8"],"warm-sand":["96681f","839324","cf3b3e","4d88b2","4a47a4"],"deep-teal":["0f6b5c","2683bb","22aa3e","b74870","a85c43"],"graphite-dark":["0369a1","2637bb","1f9977","b75948","a18a40"],"indigo-violet":["5b5bd6","7432ae","4197c8","a08f4b","7ca04b"],"spectrum":["4563ef","5b26bb","269ebb","af8746","83993d"]};
|
|
79
|
+
var STYLE_DATA_COLORS_DARK = {"business-blue":["8ab4f8","7966e0","88e7e5","d4ad91","cbce83"],"apple-mono":["f5f5f7","2997ff","2dd4bf","ff9f0a","bf5af2"],"mckinsey":["7fb2e5","6f6bdb","8ce3d8","d1a694","cbc586"],"brand-red":["ff5a66","e09e66","e788cb","91d4c2","83b4ce"],"warm-sand":["d9b36a","c2d571","df9391","97b5ce","8e89c7"],"deep-teal":["4fc3b0","7aaecd","97d8a4","c99cac","c29c8f"],"graphite-dark":["4da3ff","6c66e0","88e7dc","d4a691","cec983"],"indigo-violet":["a5a4f0","a967e0","89c3e7","d3c792","aece83"],"spectrum":["8ba0f8","9066e0","88d6e7","d4ba91","bdce83"]};
|
|
80
|
+
var GRID = {"columns":12,"gutter":0.16,"colW":0.8644,"x":[0.6,1.6244,2.6488,3.6732,4.6976,5.722,6.7464,7.7708,8.7952,9.8196,10.844,11.8684,12.733],"y":[2.35,3.27,4.19,5.11,6.03,6.9],"safe":{"top":0.4,"bottom":0.4,"left":0.6,"right":0.6}};
|
|
81
|
+
var TYPO = {"levels":[{"id":"C0","name":"封面/章节幕标题","role":"coverTitle","usage":"封面主标题、章节幕标题"},{"id":"T1","name":"页码/章节徽章","role":"caption","usage":"页码徽章、章节编号"},{"id":"T2","name":"页面主标题/结论标题","role":"h1","usage":"每页顶部结论句(research 行动标题)"},{"id":"T3","name":"副标题/语境说明","role":"lead","usage":"主标题下方导语"},{"id":"T4","name":"模块标题/图表标题","role":"h2","usage":"卡片标题、图表标题、面板标题"},{"id":"T5","name":"证据编号/轻量标签","role":"micro","usage":"Exhibit 编号、状态标签"},{"id":"T6","name":"证据块标题/小节标题","role":"h2","usage":"分栏小标题、证据块标题"},{"id":"T7","name":"正文解释段落","role":"body","usage":"证据解释、管理解读正文、表格正文"},{"id":"T8","name":"结论条文字","role":"body","usage":"so-what 结论条、核心结论框"},{"id":"T9","name":"SO WHAT 标签","role":"caption","usage":"so-what 标签行"},{"id":"T10","name":"SO WHAT 正文/业务含义","role":"body","usage":"so-what 正文、行动含义、表格行动项"},{"id":"T11","name":"图表轴/图例/刻度/微标签","role":"micro","usage":"坐标轴、图例、刻度、单位(禁用于表格正文与完整短句)"},{"id":"T12","name":"图表数据标签","role":"caption","usage":"折线点值、柱形标签、百分比标注"},{"id":"T13","name":"关键 KPI 大数字","role":"h1","usage":"KPI 大数字、hero 数值"},{"id":"T14","name":"注释/口径/来源/页脚","role":"micro","usage":"footnote、来源行、页脚、小页码"}],"tableSemanticRule":{"body":["T7","T10"],"microAllowed":["T11"],"microForbiddenFor":["表格正文","行动项","风险项","解释句","建议句","长项目符号","完整短句"]}};
|
|
82
|
+
var CONT = {"pad":{"card":0.18,"panel":0.2,"cell":0.08,"band":0.16,"soWhat":0.16,"kpi":0.18,"chart":0.1,"head":0.05,"footer":0.05,"node":0.06},"minPad":0.06,"textMetrics":{"lineFactor":1.45,"emAsciiRatio":0.52},"overflowRule":"溢出处置优先级:① 优化内容(列表化/精炼/合并/拆页/换组合版式)→ ② 调整容器尺寸与行宽 → ③ 有限度缩小字号(见 fontShrink)→ ④ 仍装不下则拆页。禁止无限缩字号硬塞,也禁止把「精简」当第一手段砍掉决策必需信息。","fontShrink":{"ladder":[15,14,13.5,13,12.5,12,11.5,11,10.5,10,9.5,9,8.5],"maxShrinkSteps":4,"floorPt":{"presentation":10,"research":9,"architecture":10},"preferContentFirst":true,"warnBelow":9.5},"continuousTextRule":"语义连续的一句话 / so-what 主句 / 结论句 / 表格单元格正文不得拆成多个独立文本框(避免异常空格、断句、基线漂移);局部加粗高亮必须用同一文本框内富文本。"};
|
|
83
|
+
var REG = {"bar":{"html":"svg-bars","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":160}},"hbar":{"html":"svg-hbars","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":100}},"stack":{"html":"svg-stack","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":160},"nativeOpt":{"barGrouping":"stacked"}},"stackline":{"html":"svg-stackline","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":40},"nativeOpt":{"barGrouping":"percentStacked"}},"line":{"html":"svg-line","pptx":"native","nativeType":"line","dataTable":"notes","min":{"vbHeightMin":160}},"dualline":{"html":"svg-dualline","pptx":"native","nativeType":"line","dataTable":"notes","min":{"vbHeightMin":160}},"area":{"html":"svg-area","pptx":"native","nativeType":"area","dataTable":"notes","min":{"vbHeightMin":160}},"donut":{"html":"svg-donut","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":140}},"multidonut":{"html":"svg-mdonut","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":140}},"pie":{"html":"svg-pie","pptx":"native","nativeType":"pie","dataTable":"notes","min":{"pxWidthMin":140}},"radar":{"html":"svg-radar","pptx":"native","nativeType":"radar","dataTable":"notes","min":{"pxWidthMin":220}},"scatter":{"html":"svg-scatter","pptx":"native","nativeType":"scatter","dataTable":"notes","min":{"vbHeightMin":180}},"bubble":{"html":"svg-bubble","pptx":"native","nativeType":"bubble","dataTable":"notes","min":{"vbHeightMin":180}},"waterfall":{"html":"svg-wf","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":170},"nativeTrick":"stacked+hiddenBase+connector"},"gauge":{"html":"svg-gauge","pptx":"native","nativeType":"doughnut","dataTable":"notes","min":{"pxWidthMin":180},"nativeTrick":"doughnut+firstSliceAng+hiddenRemainder"},"pareto":{"html":"svg-pareto","pptx":"native","nativeType":"bar","dataTable":"notes","min":{"vbHeightMin":180},"nativeTrick":"multiType+bar+line"},"gantt":{"html":"svg-gantt","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":130},"path":{"forbidPresetShapes":true}},"vsbar":{"html":"svg-vsbar","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":100},"path":{"forbidPresetShapes":true}},"progress":{"html":"svg-progress","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":100},"path":{"forbidPresetShapes":true}},"sparkline":{"html":"svg-spark","pptx":"shape","dataTable":"off","min":{"vbHeightMin":20},"path":{"forbidPresetShapes":true}},"funnel":{"html":"svg-funnel","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"minSamplePoints":8,"forbidPresetShapes":true}},"treemap":{"html":"svg-treemap","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":170},"path":{"forbidPresetShapes":true}},"marimekko":{"html":"svg-marimekko","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"boxplot":{"html":"svg-boxplot","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}},"network":{"html":"svg-network","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":200},"path":{"forbidPresetShapes":true}},"sankey":{"html":"svg-sankey","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":200},"path":{"minSamplePoints":16,"trackBothEdges":true,"forbidPresetShapes":true}},"streamgraph":{"html":"svg-stream","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":160},"path":{"minSamplePoints":24,"trackBothEdges":true,"forbidPresetShapes":true}},"slope":{"html":"svg-slope","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}},"dumbbell":{"html":"svg-dumbbell","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"lollipop":{"html":"svg-lollipop","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":140},"path":{"forbidPresetShapes":true}},"dotplot":{"html":"svg-dotplot","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":120},"path":{"forbidPresetShapes":true}},"bulletchart":{"html":"svg-bullet","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":140},"path":{"forbidPresetShapes":true}},"waffle":{"html":"svg-waffle","pptx":"shape","dataTable":"notes","min":{"vbHeightMin":160},"path":{"forbidPresetShapes":true}},"radialbar":{"html":"svg-radialbar","pptx":"shape","dataTable":"notes","min":{"pxWidthMin":200},"path":{"minSamplePoints":16,"forbidPresetShapes":true}},"rose":{"html":"svg-rose","pptx":"shape","dataTable":"notes","min":{"pxWidthMin":180},"path":{"minSamplePoints":16,"forbidPresetShapes":true}},"candlestick":{"html":"svg-candle","pptx":"shape","dataTable":"appendix","min":{"vbHeightMin":180},"path":{"forbidPresetShapes":true}}};
|
|
84
|
+
var DEEP = {"triggers":["高保真","1:1","一比一","精确还原","按图还原","正式交付","不能偏移","像素级","严格对照","逐页验收"],"complexCharts":["sankey","streamgraph","treemap","marimekko","boxplot","network","rose","radialbar","candlestick","funnel"],"checks":{"anchors":true,"containerOverflow":true,"renderCompare":true,"manifest":true},"renderCompare":{"engine":"soffice","fallback":"powerpoint-com","skipIfMissing":true,"dpi":96}};
|
|
85
|
+
var IMG = {"layouts":["full","half","bleed","grid","compare","wall"],"multiLayouts":["grid","compare","wall"],"fitEnum":["cover","contain"],"fitDefault":"cover","formats":["png","jpeg","jpg","webp","svg"],"ratioDefault":{"full":"3:1","half":"4:3","bleed":"21:9","grid":"4:3","compare":"4:3","wall":"1:1"},"ratioCssClass":{"full":"media--r3-1","half":"media--r4-3","bleed":"media--r21-9","grid":"media--r4-3","compare":"media--r4-3","wall":"media--r1-1"},"recommendedSizePx":{"full":"2400×800","half":"1400×1050","bleed":"2560×1100","grid":"900×675","compare":"1400×1050","wall":"480×480"},"maxInlineBytes":1572864,"maxTotalInlineBytes":8388608,"maxPerPage":6,"placeholderLabel":"配图占位","placeholderHintPrefix":"建议 ","placeholderHintSuffix":"px","zeroExternal":true,"qualityRule":"位图优先提供 ≥2× 显示尺寸;截图用 16:9 或 4:3 锁定比例;数据图不要用截图(应用内联 SVG 图表,可随主题变色)。"};
|
|
86
|
+
var LAYOUT_SLOTS = {"version":"0.1","pageTypes":{"points":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".grid.g-2 / .card / .ul","pptx":"points columns","minUnits":1},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar / footnoteLine"}]},"metrics":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".metric","pptx":"metric row","minUnits":4},{"id":"annotation","role":"annotation","required":false,"html":"口径行 / .sowhat","pptx":"footnoteLine / soWhatBar"}]},"kpi":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"hero","role":"primary","required":true,"html":".t-metric hero","pptx":"hero number"},{"id":"metrics","role":"secondary","required":false,"html":".metric","pptx":"support metrics"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar"}]},"exhibit":{"mode":["research","presentation"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead / .exhibit__hd","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart] / .exhibit","pptx":"nativeChart | shapeChart","channel":"registry"},{"id":"secondary","role":"secondary","required":false,"html":"要点列表","pptx":"side bullets"},{"id":"annotation","role":"annotation","required":true,"html":".exhibit__src / .sowhat","pptx":"footnoteLine / soWhatBar / notes"}]},"split":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"left","role":"primary","required":true,"html":"左区(points/table/image/chart)","pptx":"left zone"},{"id":"right","role":"secondary","required":true,"html":"右区(points/table/image/chart)","pptx":"right zone"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar"}]},"table":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".tbl-wrap table","pptx":"addTable","maxRows":{"presentation":8,"research":16}},{"id":"annotation","role":"annotation","required":false,"html":".sowhat / .note","pptx":"soWhatBar / footnoteLine"}]},"bar":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart]","pptx":"nativeChart | shapeChart","channel":"registry"},{"id":"annotation","role":"annotation","required":true,"html":"note / dataTable","pptx":"footnoteLine / notes / inline table"}]},"donut":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=donut]","pptx":"addChart doughnut"},{"id":"annotation","role":"annotation","required":false,"html":"中心合计 / 图例 / .note","pptx":"center label / legend / notes"}]},"twocol":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".cols-2","pptx":"two text columns"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"threecol":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".cols-3","pptx":"three text columns"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"halftable":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"左表","pptx":"addTable left"},{"id":"secondary","role":"secondary","required":true,"html":"右图","pptx":"chart right"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"matrix":{"mode":["research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".matrix","pptx":"matrix cells"},{"id":"annotation","role":"annotation","required":false,"html":".sowhat","pptx":"soWhatBar"}]},"comparison":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"left","role":"primary","required":true,"html":"左面板","pptx":"left panel"},{"id":"right","role":"secondary","required":true,"html":"右面板","pptx":"right panel"},{"id":"annotation","role":"annotation","required":false,"html":"verdict / .sowhat","pptx":"verdict bar"}]},"quote":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"金句正文","pptx":"quote text"}]},"timeline":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".tl","pptx":"timeline axis"}]},"steps":{"mode":["presentation","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".steps","pptx":"step row"}]},"cards":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".card grid","pptx":"card grid"}]},"heatmap":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".heat","pptx":"heat cells"}]},"bullet":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".bul","pptx":"bullet rows"}]},"pyramid":{"mode":["presentation","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".pyr","pptx":"pyramid layers"}]},"image":{"mode":["presentation","research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".media*","pptx":"imageLayoutShapes"}]},"diagram":{"mode":["architecture","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".arch / layers","pptx":"layered nodes + edges","maxNodes":24,"maxLayers":4},{"id":"annotation","role":"annotation","required":false,"html":".arch__legend / .note","pptx":"legend chips / notes"}]},"lane":{"mode":["architecture","research"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":".lane","pptx":"lanes + steps"}]},"sankey":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=sankey]","pptx":"infoSankey","maxNodes":12},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"treemap":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=treemap]","pptx":"infoTreemap"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"boxplot":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=boxplot]","pptx":"infoBoxplot"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"network":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=network]","pptx":"infoNetwork"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"marimekko":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=marimekko]","pptx":"infoMarimekko"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]},"streamgraph":{"mode":["research","architecture"],"slots":[{"id":"head","role":"chrome","required":true,"html":".shead","pptx":"head()"},{"id":"primary","role":"primary","required":true,"html":"svg[data-chart=streamgraph]","pptx":"infoStreamgraph"},{"id":"annotation","role":"annotation","required":true,"html":"dataTable / .note","pptx":"notes"}]}}};
|
|
87
|
+
|
|
88
|
+
/* regionOf(pageType, slotId, opts) — 页型布局区域 IR(与 scripts/lib_layout_regions.js 同源) */
|
|
89
|
+
function regionOf(pageType, slotId, opts) {
|
|
90
|
+
var type = String(pageType || 'points'), slot = String(slotId || '');
|
|
91
|
+
opts = opts || {};
|
|
92
|
+
var L = PT.layout || {}, C = PT.common || {};
|
|
93
|
+
var topDefault = C.bodyY || 2.5;
|
|
94
|
+
var bottomDefault = opts.withNote ? (L.contentBottomWithNote || 6.4) : (L.contentBottom || 6.9);
|
|
95
|
+
if (slot === 'head' || slot === 'chrome') {
|
|
96
|
+
return { x: MX, y: C.headEyebrowY || 0.55, w: CW,
|
|
97
|
+
h: Math.max(0.4, (C.headRuleY || 1.95) - (C.headEyebrowY || 0.55)),
|
|
98
|
+
titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY };
|
|
99
|
+
}
|
|
100
|
+
if (slot === 'annotation' || slot === 'soWhat') {
|
|
101
|
+
var sy = (PT.exhibit && PT.exhibit.soWhatY != null) ? PT.exhibit.soWhatY : 6.05;
|
|
102
|
+
return { x: MX, y: sy, w: CW, h: 0.62 };
|
|
103
|
+
}
|
|
104
|
+
if (slot === 'footnote') {
|
|
105
|
+
var fy = (PT.exhibit && PT.exhibit.footnoteY != null) ? PT.exhibit.footnoteY : ((PT.note && PT.note.y) || 6.55);
|
|
106
|
+
return { x: MX, y: fy, w: CW - 1.2, h: (PT.note && PT.note.h) || 0.32 };
|
|
107
|
+
}
|
|
108
|
+
var P = PT[type] || {};
|
|
109
|
+
if (type === 'exhibit' && slot === 'primary') {
|
|
110
|
+
var ey = P.chartY || 2.95;
|
|
111
|
+
return { x: MX, y: ey, w: CW, h: Math.max(1.2, (L.contentBottomWithNote || 6.4) - ey), badgeY: P.badgeY };
|
|
112
|
+
}
|
|
113
|
+
if (type === 'metrics' && slot === 'primary') {
|
|
114
|
+
return { x: MX, y: P.bandY || 3.1, w: CW, h: P.cardH || 2.3, gap: P.gap, maxW: P.maxW,
|
|
115
|
+
valY: P.valY, valH: P.valH, capY: P.capY, capH: P.capH };
|
|
116
|
+
}
|
|
117
|
+
if (type === 'table' && slot === 'primary') {
|
|
118
|
+
var ty = P.y || topDefault;
|
|
119
|
+
return { x: MX, y: ty, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - ty) };
|
|
120
|
+
}
|
|
121
|
+
if (type === 'bar' && slot === 'primary') {
|
|
122
|
+
var by = P.chartY || 2.95;
|
|
123
|
+
var bb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
124
|
+
return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
|
|
125
|
+
}
|
|
126
|
+
if (type === 'bar' && slot === 'hbar') {
|
|
127
|
+
var y0 = P.hbarY0 || 2.6;
|
|
128
|
+
var y1 = opts.bottom != null ? opts.bottom : (P.hbarY1 || 6.25);
|
|
129
|
+
return { x: MX, y: y0, w: CW, h: Math.max(1.2, y1 - y0) };
|
|
130
|
+
}
|
|
131
|
+
if (type === 'points' && (slot === 'primary' || slot === 'list')) {
|
|
132
|
+
var ly = P.listY || topDefault;
|
|
133
|
+
return { x: MX, y: ly, w: CW, h: Math.max(0.8, (opts.bottom != null ? opts.bottom : bottomDefault) - ly) };
|
|
134
|
+
}
|
|
135
|
+
if (type === 'split' && (slot === 'left' || slot === 'right')) {
|
|
136
|
+
var st = P.chartY || topDefault;
|
|
137
|
+
var sb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
138
|
+
if (slot === 'left') return { x: MX, y: st, w: CW * (P.leftW || 0.55), h: Math.max(1.2, sb - st) };
|
|
139
|
+
return { x: MX + CW * (P.rightX != null ? P.rightX : 0.585), y: st, w: CW * (P.rightW || 0.415), h: Math.max(1.2, sb - st) };
|
|
140
|
+
}
|
|
141
|
+
if (type === 'twocol' && (slot === 'primary' || slot === 'left' || slot === 'right')) {
|
|
142
|
+
var gap = P.colGap || 0.5, colW = (CW - gap) / 2;
|
|
143
|
+
var tt = opts.top != null ? opts.top : topDefault;
|
|
144
|
+
var tb = opts.bottom != null ? opts.bottom : bottomDefault;
|
|
145
|
+
if (slot === 'right') return { x: MX + colW + gap, y: tt, w: colW, h: Math.max(1, tb - tt) };
|
|
146
|
+
return { x: MX, y: tt, w: colW, h: Math.max(1, tb - tt) };
|
|
147
|
+
}
|
|
148
|
+
if ((type === 'diagram' || type === 'lane') && slot === 'primary') {
|
|
149
|
+
var arch = PT.arch || {};
|
|
150
|
+
var dy = (opts.mode === 'architecture' && arch.fullStartY != null) ? arch.fullStartY : (P.bodyStartY || 2.9);
|
|
151
|
+
var db = (opts.mode === 'architecture' && arch.fullEndY != null) ? arch.fullEndY
|
|
152
|
+
: (opts.bottom != null ? opts.bottom : bottomDefault);
|
|
153
|
+
return { x: MX, y: dy, w: CW, h: Math.max(1.2, db - dy) };
|
|
154
|
+
}
|
|
155
|
+
if (type === 'kpi' && (slot === 'hero' || slot === 'primary')) {
|
|
156
|
+
return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
|
|
157
|
+
dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
|
|
158
|
+
}
|
|
159
|
+
if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
|
|
160
|
+
var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
|
|
161
|
+
if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
162
|
+
return { x: MX, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
|
|
163
|
+
}
|
|
164
|
+
if (type === 'quote' && slot === 'primary') {
|
|
165
|
+
return { x: MX + 1.05, y: P.textY || 2.7, w: CW - 2.1, h: P.textH || 2.3 };
|
|
166
|
+
}
|
|
167
|
+
if (type === 'heatmap' && slot === 'primary') {
|
|
168
|
+
var hy = P.y || 2.6;
|
|
169
|
+
return { x: MX, y: hy, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - hy) };
|
|
170
|
+
}
|
|
171
|
+
if (type === 'bullet' && slot === 'primary') {
|
|
172
|
+
var uy = P.y || 2.6;
|
|
173
|
+
return { x: MX, y: uy, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - uy) };
|
|
174
|
+
}
|
|
175
|
+
if (type === 'pyramid' && slot === 'primary') {
|
|
176
|
+
var py2 = P.y || 2.35;
|
|
177
|
+
return { x: MX, y: py2, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - py2) };
|
|
178
|
+
}
|
|
179
|
+
if (type === 'steps' && slot === 'primary') {
|
|
180
|
+
return { x: MX, y: P.y || 3.05, w: CW, h: P.barH || 1.35 };
|
|
181
|
+
}
|
|
182
|
+
if (type === 'cards' && slot === 'primary') {
|
|
183
|
+
var cy2 = P.startY || 3.0;
|
|
184
|
+
return { x: MX, y: cy2, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - cy2) };
|
|
185
|
+
}
|
|
186
|
+
if (type === 'timeline' && slot === 'primary') {
|
|
187
|
+
return { x: MX, y: P.labelY || 2.95, w: CW, h: Math.max(1, bottomDefault - (P.labelY || 2.95)) };
|
|
188
|
+
}
|
|
189
|
+
if (type === 'image' && slot === 'primary') {
|
|
190
|
+
var iy2 = Math.max(P.y || 2.3, opts.top || 0);
|
|
191
|
+
return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, (opts.bottom != null ? opts.bottom : bottomDefault) - iy2)) };
|
|
192
|
+
}
|
|
193
|
+
if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
|
|
194
|
+
var rs = PT.research || {};
|
|
195
|
+
return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
|
|
196
|
+
}
|
|
197
|
+
if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
|
|
198
|
+
var rs2 = PT.research || {};
|
|
199
|
+
return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
|
|
200
|
+
}
|
|
201
|
+
if (type === 'matrix' && slot === 'primary') {
|
|
202
|
+
var rm = PT.research || {};
|
|
203
|
+
var my = rm.matrixY || 2.3;
|
|
204
|
+
return { x: MX, y: my, w: CW, h: Math.max(1, (opts.bottom != null ? opts.bottom : bottomDefault) - my) };
|
|
205
|
+
}
|
|
206
|
+
var INFO = { sankey:1, treemap:1, boxplot:1, network:1, marimekko:1, streamgraph:1 };
|
|
207
|
+
if (INFO[type] && slot === 'primary') {
|
|
208
|
+
var iy3 = P.y || topDefault;
|
|
209
|
+
var ih3 = Math.max(1.2, Math.min(P.h || 4.1, (opts.bottom != null ? opts.bottom : bottomDefault) - iy3));
|
|
210
|
+
return { x: MX, y: iy3, w: CW, h: ih3 };
|
|
211
|
+
}
|
|
212
|
+
return { x: MX, y: topDefault, w: CW, h: Math.max(0.8, bottomDefault - topDefault) };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* __TOPPPT_CONSTANTS_END__ */
|
|
216
|
+
|
|
217
|
+
/* __TOPPPT_SCHEMA_START__ */
|
|
218
|
+
/* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
|
|
219
|
+
/* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
|
|
220
|
+
var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":16},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…]},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"]},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
|
|
221
|
+
/* __TOPPPT_SCHEMA_END__ */
|
|
222
|
+
|
|
223
|
+
var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
|
|
224
|
+
/* 三模式独立排版比例尺:调用点给 presentation 基准 pt → 取最接近的 TS_BASE 角色 →
|
|
225
|
+
查 MTS[mode][role]。research 是咨询密排比例尺(正文 10.5pt),不再是简单 ×0.8。
|
|
226
|
+
未知模式回落 presentation 档(MTS 与 mode 取值同源于 model-schema.json 的 modes)。 */
|
|
227
|
+
function modeSize(base) {
|
|
228
|
+
var mts = (typeof MTS !== 'undefined' && MTS) ? (MTS[_mode] || MTS.presentation) : null;
|
|
229
|
+
if (!mts) return base;
|
|
230
|
+
var best = null, bd = 1e9;
|
|
231
|
+
for (var k in TS_BASE) {
|
|
232
|
+
if (!TS_BASE.hasOwnProperty(k)) continue;
|
|
233
|
+
var d = Math.abs(TS_BASE[k] - base);
|
|
234
|
+
if (d < bd) { bd = d; best = mts[k]; }
|
|
235
|
+
}
|
|
236
|
+
/* 钳制与取整必须与 build_pptx.js 的 sz() 逐字一致——同一张表配不同后处理 = 两通道字号漂移 */
|
|
237
|
+
return Math.max(8.5, Math.round((best == null ? base : best) * 2) / 2);
|
|
238
|
+
}
|
|
239
|
+
/* 编码色板:9 套风格**各有自己的** c1–c5(单源 styleDataColors / styleDataColorsDark)。
|
|
240
|
+
与 HTML 侧的 .f-c1~c5 语义类同源;结构色纪律不受影响——本表只用于数据系列。 */
|
|
241
|
+
function dataColors(style, theme) {
|
|
242
|
+
var dark = (theme === 'dark');
|
|
243
|
+
var map = (dark && typeof STYLE_DATA_COLORS_DARK !== 'undefined' && STYLE_DATA_COLORS_DARK)
|
|
244
|
+
? STYLE_DATA_COLORS_DARK
|
|
245
|
+
: (typeof STYLE_DATA_COLORS !== 'undefined' ? STYLE_DATA_COLORS : null);
|
|
246
|
+
if (!map) return null;
|
|
247
|
+
var c = map[style];
|
|
248
|
+
return (c && c.length) ? c : null; // null = 用 S 的中性色(在调用处映射)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ── 版式安全边界(与 build_pptx.js 同一常量单源) ── */
|
|
252
|
+
var LAY = PT.layout || {};
|
|
253
|
+
var CONTENT_TOP = (LAY.contentTop != null) ? LAY.contentTop : 2.5;
|
|
254
|
+
var CONTENT_BOTTOM = (LAY.contentBottom != null) ? LAY.contentBottom : 6.9;
|
|
255
|
+
|
|
256
|
+
/* ── 自适应排版工具(与 build_pptx.js 同算法,保证两通道字号/换行一致) ── */
|
|
257
|
+
function estLines(text, widthIn, fontPt) {
|
|
258
|
+
var usable = Math.max(24, widthIn * 72 - 8), w = 0, lines = 1;
|
|
259
|
+
var s = String(text == null ? '' : text);
|
|
260
|
+
for (var i = 0; i < s.length; i++) {
|
|
261
|
+
if (s[i] === '\n') { lines++; w = 0; continue; }
|
|
262
|
+
var cw = /[\x00-\xff]/.test(s[i]) ? fontPt * 0.52 : fontPt;
|
|
263
|
+
if (w + cw > usable) { lines++; w = cw; } else { w += cw; }
|
|
264
|
+
}
|
|
265
|
+
return lines;
|
|
266
|
+
}
|
|
267
|
+
function estTextH(text, widthIn, fontPt, lineFactor) {
|
|
268
|
+
return estLines(text, widthIn, fontPt) * fontPt * (lineFactor || 1.45) / 72;
|
|
269
|
+
}
|
|
270
|
+
var FONT_LADDER = [15, 14, 13.5, 13, 12.5, 12, 11.5, 11, 10.5, 10, 9.5, 9, 8.5];
|
|
271
|
+
/* 有限缩字号(v8.2,与 build_pptx.js 同策略):先优化内容/组合/拆页,最后才缩字号。
|
|
272
|
+
maxShrinkSteps 限制下探步数;floorPt 按模式绝对下限。 */
|
|
273
|
+
function modeFloorPtA(mode) {
|
|
274
|
+
var floors = (typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.floorPt) ||
|
|
275
|
+
{ presentation: 10, research: 9, architecture: 10 };
|
|
276
|
+
var m = String(mode || 'presentation');
|
|
277
|
+
return (floors[m] != null) ? floors[m] : 10;
|
|
278
|
+
}
|
|
279
|
+
function fitFont(items, widthIn, availIn, opts) {
|
|
280
|
+
opts = opts || {};
|
|
281
|
+
var max = (opts.max == null) ? 99 : opts.max, lf = opts.lineFactor || 1.45;
|
|
282
|
+
var gapF = (opts.gapFactor == null) ? 0.5 : opts.gapFactor;
|
|
283
|
+
var mode = (typeof CONTENT !== 'undefined' && CONTENT && CONTENT.mode) ||
|
|
284
|
+
(typeof TOPPPT_MODE !== 'undefined' ? TOPPPT_MODE : 'presentation');
|
|
285
|
+
var floor = (opts.floor != null) ? opts.floor : modeFloorPtA(mode);
|
|
286
|
+
var maxSteps = (opts.maxShrinkSteps != null) ? opts.maxShrinkSteps
|
|
287
|
+
: ((typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.maxShrinkSteps != null)
|
|
288
|
+
? CONT.fontShrink.maxShrinkSteps : 4);
|
|
289
|
+
var baseLadder = (typeof CONT !== 'undefined' && CONT && CONT.fontShrink && CONT.fontShrink.ladder) || FONT_LADDER;
|
|
290
|
+
var raw = baseLadder.filter(function (v) { return v <= max && v >= floor; });
|
|
291
|
+
var needH = function (f) {
|
|
292
|
+
var h = 0;
|
|
293
|
+
for (var i = 0; i < items.length; i++) h += estTextH(items[i], widthIn, f, lf) + f * gapF / 72;
|
|
294
|
+
return h + (opts.pad || 0);
|
|
295
|
+
};
|
|
296
|
+
for (var li = 0; li < raw.length; li++) {
|
|
297
|
+
if (needH(raw[li]) <= availIn) return raw[li];
|
|
298
|
+
}
|
|
299
|
+
var preferred = raw.length ? raw[0] : floor;
|
|
300
|
+
var prefIdx = baseLadder.indexOf(preferred);
|
|
301
|
+
var limited = baseLadder.slice(prefIdx, prefIdx + 1 + maxSteps)
|
|
302
|
+
.filter(function (v) { return v >= floor && v <= max; });
|
|
303
|
+
for (var lj = 0; lj < limited.length; lj++) {
|
|
304
|
+
if (needH(limited[lj]) <= availIn) return limited[lj];
|
|
305
|
+
}
|
|
306
|
+
if (limited.length) return limited[limited.length - 1];
|
|
307
|
+
return floor;
|
|
308
|
+
}
|
|
309
|
+
function cols(total, n, gap) {
|
|
310
|
+
var w = (total - (n - 1) * (gap || 0)) / Math.max(1, n);
|
|
311
|
+
return { w: w, step: w + (gap || 0) };
|
|
312
|
+
}
|
|
313
|
+
/* 行高收敛:与 build_pptx.js 同算法(宁可行高更小也不越界) */
|
|
314
|
+
function fitRowH(avail, n, pref, floor) {
|
|
315
|
+
var lo = (floor == null) ? 0.22 : floor;
|
|
316
|
+
return Math.max(lo, Math.min(pref, avail / Math.max(1, n)));
|
|
317
|
+
}
|
|
318
|
+
function chartBottom(hasSoWhat, hasFootnote) {
|
|
319
|
+
if (hasFootnote) return PT.exhibit.footnoteY - 0.12;
|
|
320
|
+
if (hasSoWhat) return PT.exhibit.soWhatY - 0.12;
|
|
321
|
+
return CONTENT_BOTTOM;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* ── XML 工具 ── */
|
|
325
|
+
function esc(s) {
|
|
326
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
327
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
328
|
+
}
|
|
329
|
+
function E(v) { return Math.round(v * EMU); }
|
|
330
|
+
function solid(hex) { return '<a:solidFill><a:srgbClr val="' + hex + '"/></a:solidFill>'; }
|
|
331
|
+
|
|
332
|
+
var _id = 10;
|
|
333
|
+
function nid() { return ++_id; }
|
|
334
|
+
|
|
335
|
+
/* 文本框:paras = [[{t,b,sz,col,font,spc}…]…](每个内层数组=一段,可混排多 run)
|
|
336
|
+
opts.lineSpacing / opts.spaceBefore 为基准 pt(与 build_pptx.js 的 lineSpacing/paraSpaceBefore 同源),
|
|
337
|
+
序列化时统一过 modeSize 映射,保证两通道行距一致。 */
|
|
338
|
+
function txSp(x, y, w, h, paras, opts) {
|
|
339
|
+
opts = opts || {};
|
|
340
|
+
var pPr = '';
|
|
341
|
+
if (opts.lineSpacing) pPr += '<a:lnSpc><a:spcPts val="' + Math.round(modeSize(opts.lineSpacing) * 100) + '"/></a:lnSpc>';
|
|
342
|
+
if (opts.spaceBefore) pPr += '<a:spcBef><a:spcPts val="' + Math.round(modeSize(opts.spaceBefore) * 100) + '"/></a:spcBef>';
|
|
343
|
+
var pPrXml = (opts.align || pPr)
|
|
344
|
+
? '<a:pPr' + (opts.align ? ' algn="' + opts.align + '"' : '') + '>' + pPr + '</a:pPr>'
|
|
345
|
+
: '';
|
|
346
|
+
var body = paras.map(function (runs) {
|
|
347
|
+
var rs = runs.map(function (r) {
|
|
348
|
+
var pr = '<a:rPr lang="zh-CN" sz="' + Math.round(modeSize(r.sz) * 100) + '"' +
|
|
349
|
+
(r.b ? ' b="1"' : '') + (r.spc ? ' spc="' + r.spc + '"' : '') + '>' +
|
|
350
|
+
solid(r.col) +
|
|
351
|
+
'<a:latin typeface="' + esc(r.font) + '"/><a:ea typeface="' + esc(r.font) + '"/></a:rPr>';
|
|
352
|
+
return '<a:r>' + pr + '<a:t>' + esc(r.t) + '</a:t></a:r>';
|
|
353
|
+
}).join('');
|
|
354
|
+
return '<a:p>' + pPrXml + rs + '</a:p>';
|
|
355
|
+
}).join('');
|
|
356
|
+
var fill = opts.fill ? solid(opts.fill) : '<a:noFill/>';
|
|
357
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="tx"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>' +
|
|
358
|
+
'<p:spPr><a:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(w) + '" cy="' + E(h) + '"/></a:xfrm>' +
|
|
359
|
+
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>' + fill + '<a:ln><a:noFill/></a:ln></p:spPr>' +
|
|
360
|
+
'<p:txBody><a:bodyPr wrap="square" anchor="' + (opts.anchor || 't') + '"><a:normAutofit/></a:bodyPr><a:lstStyle/>' +
|
|
361
|
+
body + '</p:txBody></p:sp>';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* 形状:rect / roundRect / ellipse / pie(opts.av = avLst 原始内联 XML,供 donut 楔形角度) */
|
|
365
|
+
function shape(geom, x, y, w, h, fillHex, opts) {
|
|
366
|
+
opts = opts || {};
|
|
367
|
+
var av;
|
|
368
|
+
if (opts.av) av = '<a:avLst>' + opts.av + '</a:avLst>';
|
|
369
|
+
else if (geom === 'roundRect') av = '<a:avLst><a:gd name="adj" fmla="val ' + (opts.adj || 8000) + '"/></a:avLst>';
|
|
370
|
+
else av = '<a:avLst/>';
|
|
371
|
+
var ln = opts.line
|
|
372
|
+
? '<a:ln w="9525">' + (opts.dash ? '<a:prstDash val="dash"/>' : '') + solid(opts.line) + '</a:ln>'
|
|
373
|
+
: '<a:ln><a:noFill/></a:ln>';
|
|
374
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="' + (opts.name || 'sh') + '"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>' +
|
|
375
|
+
'<p:spPr><a:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(Math.max(w, 0.01)) + '" cy="' + E(Math.max(h, 0.01)) + '"/></a:xfrm>' +
|
|
376
|
+
'<a:prstGeom prst="' + geom + '">' + av + '</a:prstGeom>' + solid(fillHex) + ln + '</p:spPr>' +
|
|
377
|
+
'<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang="zh-CN"/></a:p></p:txBody></p:sp>';
|
|
378
|
+
}
|
|
379
|
+
/* pie 楔形 avLst:角度(度)× 60000;donut 页型两通道共用同一角度拆分规则 */
|
|
380
|
+
function pieAv(a1, a2) {
|
|
381
|
+
return '<a:gd name="adj1" fmla="val ' + Math.round(a1 * 60000) + '"/>' +
|
|
382
|
+
'<a:gd name="adj2" fmla="val ' + Math.round(a2 * 60000) + '"/>';
|
|
383
|
+
}
|
|
384
|
+
/* 环形图(donut 页型):原生 pie 楔形 + 背景色圆(挖孔)+ 中心文字 + 右侧图例。
|
|
385
|
+
角度约定:12 点钟(270°)起顺时针;跨 360° 的楔形拆成两段(≤359.9 封顶)。 */
|
|
386
|
+
function donutWedges(labels, values) {
|
|
387
|
+
var total = 0;
|
|
388
|
+
(values || []).forEach(function (v) { total += Math.max(0, v); });
|
|
389
|
+
var segs = [], a = 270;
|
|
390
|
+
(values || []).forEach(function (v, i) {
|
|
391
|
+
var frac = total > 0 ? Math.max(0, v) / total : 0;
|
|
392
|
+
var b = a + frac * 360;
|
|
393
|
+
var s = a % 360, e = b % 360;
|
|
394
|
+
if (frac <= 0) { a = b; return; }
|
|
395
|
+
if (b - a >= 359.9) { segs.push([0, 359.9, i]); }
|
|
396
|
+
else if (b <= 360) { segs.push([a, Math.min(b, 359.9), i]); }
|
|
397
|
+
else { segs.push([a, 359.9, i]); if (e > 0.1) segs.push([0, e, i]); }
|
|
398
|
+
a = b;
|
|
399
|
+
});
|
|
400
|
+
return segs;
|
|
401
|
+
}
|
|
402
|
+
function donutChart(sh, ec, S, dcols) {
|
|
403
|
+
var d = PT.donut, labels = ec.labels || [], values = ec.values || [];
|
|
404
|
+
var segs = donutWedges(labels, values);
|
|
405
|
+
var bx = d.centerX - d.r, by = d.centerY - d.r, bw = d.r * 2;
|
|
406
|
+
segs.forEach(function (sg) {
|
|
407
|
+
sh.push(shape('pie', bx, by, bw, bw, dcols[sg[2] % dcols.length], { av: pieAv(sg[0], sg[1]) }));
|
|
408
|
+
});
|
|
409
|
+
var hb = d.holeR * 2;
|
|
410
|
+
sh.push(shape('ellipse', d.centerX - d.holeR, d.centerY - d.holeR, hb, hb, S.bg));
|
|
411
|
+
var total = 0; values.forEach(function (v) { total += Math.max(0, v); });
|
|
412
|
+
var totalStr = (Math.round(total * 10) / 10) + (ec.unit || '');
|
|
413
|
+
sh.push(txSp(d.centerX - d.holeR, d.centerY - 0.42, hb, 0.5,
|
|
414
|
+
[[{ t: totalStr, sz: 20, b: 1, col: S.ink, font: S.fontDisplay }]], { align: 'ctr', anchor: 'ctr' }));
|
|
415
|
+
sh.push(txSp(d.centerX - d.holeR, d.centerY + 0.08, hb, 0.35,
|
|
416
|
+
[[{ t: ec.centerLabel || '合计', sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
417
|
+
var n = Math.max(1, labels.length);
|
|
418
|
+
var rowH = Math.min(d.legendRowH, 3.4 / n);
|
|
419
|
+
var ly0 = d.centerY - (n * rowH) / 2 + 0.1;
|
|
420
|
+
var sum2 = total || 1;
|
|
421
|
+
labels.forEach(function (lb, i) {
|
|
422
|
+
var v = values[i] || 0;
|
|
423
|
+
var y = ly0 + i * rowH;
|
|
424
|
+
sh.push(shape('ellipse', d.legendX, y + 0.08, 0.16, 0.16, dcols[i % dcols.length]));
|
|
425
|
+
sh.push(txSp(d.legendX + 0.3, y, d.legendLabelW, rowH,
|
|
426
|
+
[[{ t: lb, sz: 12, b: 1, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
427
|
+
sh.push(txSp(d.legendX + 3.8, y, d.legendValW, rowH,
|
|
428
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 12, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
429
|
+
sh.push(txSp(d.legendPctX, y, d.legendPctW, rowH,
|
|
430
|
+
[[{ t: (Math.round(v / sum2 * 1000) / 10) + '%', sz: 10.5, col: S.faint, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function donutColors(style, S, theme) {
|
|
434
|
+
var d = dataColors(style, theme);
|
|
435
|
+
return d || [S.accent, S.faint, S.body, S.line];
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* 表格(graphicFrame + a:tbl);rows[0]=表头;行高按可用高度自适应(与 B 通道同规则) */
|
|
439
|
+
function table(x, y, w, colW, rows, S, opts) {
|
|
440
|
+
opts = opts || {};
|
|
441
|
+
var n = rows.length;
|
|
442
|
+
var maxRowH = (opts.maxRowH == null) ? PT.table.rowH : opts.maxRowH;
|
|
443
|
+
var minRowH = (opts.minRowH == null) ? PT.table.rowHMin : opts.minRowH;
|
|
444
|
+
var availH = (opts.availH == null) ? (CONTENT_BOTTOM - y) : opts.availH;
|
|
445
|
+
var rowH = Math.max(minRowH, Math.min(maxRowH, availH / Math.max(1, n)));
|
|
446
|
+
var dense = rowH < 0.42;
|
|
447
|
+
var total = colW.reduce(function (a, b) { return a + b; }, 0);
|
|
448
|
+
var grid = colW.map(function (cw) { return '<a:gridCol w="' + E(cw) + '"/>'; }).join('');
|
|
449
|
+
var trs = rows.map(function (row, ri) {
|
|
450
|
+
var tcs = row.map(function (cell) {
|
|
451
|
+
var isHead = ri === 0;
|
|
452
|
+
var fz = isHead ? (dense ? 11 : 13) : (dense ? 10 : 12.5);
|
|
453
|
+
return '<a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r>' +
|
|
454
|
+
'<a:rPr lang="zh-CN" sz="' + Math.round(modeSize(fz) * 100) + '"' + (isHead ? ' b="1"' : '') + '>' +
|
|
455
|
+
solid(isHead ? S.onAccent : S.body) +
|
|
456
|
+
'<a:latin typeface="' + S.font + '"/><a:ea typeface="' + S.font + '"/></a:rPr>' +
|
|
457
|
+
'<a:t>' + esc(cell) + '</a:t></a:r></a:p></a:txBody>' +
|
|
458
|
+
'<a:tcPr marL="91440" marR="91440" marT="45720" marB="45720" anchor="ctr">' +
|
|
459
|
+
solid(isHead ? S.accent : S.bg) + '</a:tcPr></a:tc>';
|
|
460
|
+
}).join('');
|
|
461
|
+
return '<a:tr h="' + E(rowH) + '">' + tcs + '</a:tr>';
|
|
462
|
+
}).join('');
|
|
463
|
+
/* OOXML 规范:p:graphicFrame 的 graphic 子元素属 DrawingML 命名空间(a:graphic,与 pptxgenjs 一致) */
|
|
464
|
+
return '<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="' + nid() + '" name="tbl"/>' +
|
|
465
|
+
'<p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr><p:nvPr/></p:nvGraphicFramePr>' +
|
|
466
|
+
'<p:xfrm><a:off x="' + E(x) + '" y="' + E(y) + '"/><a:ext cx="' + E(w) + '" cy="' + E(rowH * rows.length) + '"/></p:xfrm>' +
|
|
467
|
+
'<a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">' +
|
|
468
|
+
'<a:tbl><a:tblPr firstRow="1" bandRow="0"/><a:tblGrid>' + grid + '</a:tblGrid>' + trs + '</a:tbl>' +
|
|
469
|
+
'</a:graphicData></a:graphic></p:graphicFrame>';
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* so-what 结论条 / 页脚来源行(research 页通用可选件,与 HTML 同源版式;区域走布局 IR) */
|
|
473
|
+
function soWhatBar(sh, text, S) {
|
|
474
|
+
var R = (typeof regionOf === 'function') ? regionOf('exhibit', 'annotation')
|
|
475
|
+
: { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
|
|
476
|
+
/* 与 B 通道同算法:长结论条有限缩字号,优先保容器不溢出 */
|
|
477
|
+
var bodyW = Math.max(2.5, R.w - 0.44 - 1.35);
|
|
478
|
+
var fzB = fitFont([String(text || '')], bodyW, R.h - 0.14, { max: 12.5, gapFactor: 0.2, maxShrinkSteps: 3 });
|
|
479
|
+
sh.push(shape('rect', R.x, R.y, R.w, R.h, S.soft));
|
|
480
|
+
sh.push(shape('rect', R.x, R.y, 0.06, R.h, S.accent));
|
|
481
|
+
sh.push(txSp(R.x + 0.22, R.y + 0.06, R.w - 0.44, R.h - 0.12,
|
|
482
|
+
[[{ t: 'SO WHAT ', sz: 11, b: 1, col: S.accent, font: S.font, spc: 150 },
|
|
483
|
+
{ t: text, sz: fzB, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
484
|
+
}
|
|
485
|
+
function footnoteLine(sh, text, S) {
|
|
486
|
+
var R = (typeof regionOf === 'function') ? regionOf('exhibit', 'footnote')
|
|
487
|
+
: { x: MX, y: PT.exhibit.footnoteY, w: CW - 1.2, h: 0.32 };
|
|
488
|
+
sh.push(txSp(R.x, R.y, R.w, R.h,
|
|
489
|
+
[[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
|
|
490
|
+
}
|
|
491
|
+
/* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
|
|
492
|
+
function flagBar(sh, items, S, yTop) {
|
|
493
|
+
var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
|
|
494
|
+
var list = (items || []).slice(0, F.maxRows).map(String);
|
|
495
|
+
if (!list.length) return 0;
|
|
496
|
+
var h = F.hdH + list.length * F.rowH + 0.06;
|
|
497
|
+
var y = (yTop != null) ? yTop : (CONTENT_BOTTOM - h);
|
|
498
|
+
sh.push(shape('rect', MX, y, CW, h, S.soft));
|
|
499
|
+
sh.push(shape('rect', MX, y, 0.055, h, S.accent));
|
|
500
|
+
sh.push(txSp(MX + 0.22, y + 0.02, CW - 0.44, F.hdH,
|
|
501
|
+
[[{ t: '待核实 · 需二次确认', sz: 9.5, b: 1, col: S.accent, font: S.font, spc: 100 }]]));
|
|
502
|
+
list.forEach(function (t, i) {
|
|
503
|
+
sh.push(txSp(MX + 0.24, y + F.hdH + i * F.rowH, CW - 0.5, F.rowH,
|
|
504
|
+
[[{ t: '· ' + t, sz: 9.5, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
505
|
+
});
|
|
506
|
+
return h;
|
|
507
|
+
}
|
|
508
|
+
/* 素材图片:预览引擎只产出 slide XML(无 media part)→ 以中性图位块表示;
|
|
509
|
+
配图占位(placeholder)额外发射标签文本——标签串由 imageSpec 常量拼接,与 B 通道**同串**
|
|
510
|
+
(cross_verify 逐页比对)。真实图片不发射任何文本,保证 A/B 文本集合一致。 */
|
|
511
|
+
function imgPlaceholderLabel(layout, multi) {
|
|
512
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
513
|
+
var label = IS.placeholderLabel || '配图占位';
|
|
514
|
+
if (multi) return label;
|
|
515
|
+
var size = (IS.recommendedSizePx || {})[layout];
|
|
516
|
+
if (!size) return label;
|
|
517
|
+
return label + ' · ' + (IS.placeholderHintPrefix || '建议 ') + size + (IS.placeholderHintSuffix || 'px');
|
|
518
|
+
}
|
|
519
|
+
function addImageEl(sh, src, S, x, y, w, h, opts) {
|
|
520
|
+
opts = opts || {};
|
|
521
|
+
var ph = !!opts.placeholder || !src;
|
|
522
|
+
/* name="img"/"imgph":预览模态据此把图位替换为真实图片(ui.js renderSlide),
|
|
523
|
+
不影响文本集合(cross_verify 只比文本)。 */
|
|
524
|
+
sh.push(shape('roundRect', x, y, w, h, S.surface,
|
|
525
|
+
{ line: S.line, dash: ph, adj: 4000, name: ph ? 'imgph' : 'img' }));
|
|
526
|
+
if (ph) {
|
|
527
|
+
sh.push(txSp(x + 0.08, y + h / 2 - 0.22, Math.max(0.4, w - 0.16), 0.44,
|
|
528
|
+
[[{ t: imgPlaceholderLabel(opts.layout || 'full', !!opts.multi), sz: 11, b: 1, col: S.faint, font: S.font }]],
|
|
529
|
+
{ align: 'ctr', anchor: 'ctr' }));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/* 版式比例(imageSpec.ratioDefault 或 image.ratio,形如 "3:1")→ 数值宽高比。
|
|
533
|
+
双引擎同源:HTML 用同比例的 .media--r* 锁定类,PPTX 用本函数算高度,版式才一致。 */
|
|
534
|
+
function imgRatio(layout, img) {
|
|
535
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
536
|
+
var raw = String((img && img.ratio) || (IS.ratioDefault || {})[layout] || '3:1');
|
|
537
|
+
var m = raw.match(/^\s*(\d+(?:\.\d+)?)\s*[:/×x]\s*(\d+(?:\.\d+)?)/);
|
|
538
|
+
if (!m) return 3;
|
|
539
|
+
var a = parseFloat(m[1]), b = parseFloat(m[2]);
|
|
540
|
+
return (a > 0 && b > 0) ? a / b : 3;
|
|
541
|
+
}
|
|
542
|
+
/* 图片版式族(与 HTML .media / .media-grid / .media-compare / .media-wall 同源):
|
|
543
|
+
full 版心全宽|half 左图右注|bleed 通栏出血|grid 多图网格|compare 双图对比|wall Logo 墙。
|
|
544
|
+
每版式按 imageSpec.ratioDefault 锁定比例;空间不足时按可用高度收敛并**垂直居中**
|
|
545
|
+
(避免"图贴顶、下方大片留白")。返回图注基准 Y。占位符走原生形状,pictures 不增。 */
|
|
546
|
+
function imageLayoutShapes(sh, S, layout, img, items, points, isPh, fit, y0, y1) {
|
|
547
|
+
var IM = PT.image || {};
|
|
548
|
+
var gap = IM.gridGap || 0.16;
|
|
549
|
+
var iy = y0;
|
|
550
|
+
var ih = Math.max(1.0, y1 - iy);
|
|
551
|
+
var capY = iy + ih;
|
|
552
|
+
var ratio = imgRatio(layout, img);
|
|
553
|
+
function one(x, y, w, h, it, multi) {
|
|
554
|
+
var ph = isPh || !(it && it.src);
|
|
555
|
+
addImageEl(sh, (it && it.src) || '', S, x, y, w, h,
|
|
556
|
+
{ placeholder: ph, layout: layout, multi: multi, fit: fit });
|
|
557
|
+
}
|
|
558
|
+
if (layout === 'grid' && items.length) {
|
|
559
|
+
var n = Math.min(items.length, (IMG && IMG.maxPerPage) || 6);
|
|
560
|
+
var colsN = (IM.gridCols || {})[String(n)] || Math.min(3, n);
|
|
561
|
+
var rowsN = Math.max(1, Math.ceil(n / colsN));
|
|
562
|
+
var capH = IM.gridCapH || 0.28;
|
|
563
|
+
var cellW = (CW - (colsN - 1) * gap) / colsN;
|
|
564
|
+
var capRows = rowsN * (capH + 0.04);
|
|
565
|
+
var cellH = Math.min(cellW / ratio, Math.max(0.5, (ih - (rowsN - 1) * gap - capRows) / rowsN));
|
|
566
|
+
var blockH = rowsN * cellH + (rowsN - 1) * gap + capRows;
|
|
567
|
+
var gy = iy + Math.max(0, (ih - blockH) / 2);
|
|
568
|
+
items.slice(0, n).forEach(function (it, i) {
|
|
569
|
+
var cx = MX + (i % colsN) * (cellW + gap);
|
|
570
|
+
var cy = gy + Math.floor(i / colsN) * (cellH + gap + capH + 0.04);
|
|
571
|
+
var cap = (it && it.caption) || '';
|
|
572
|
+
one(cx, cy, cellW, cellH, it, true);
|
|
573
|
+
if (cap) sh.push(txSp(cx, cy + cellH + 0.02, cellW, capH,
|
|
574
|
+
[[{ t: cap, sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
575
|
+
});
|
|
576
|
+
capY = gy + blockH;
|
|
577
|
+
} else if (layout === 'compare' && items.length >= 2) {
|
|
578
|
+
var cgap = IM.compareGap || 0.3;
|
|
579
|
+
var cw2 = (CW - cgap) / 2;
|
|
580
|
+
var capH2 = 0.34;
|
|
581
|
+
var ih2 = Math.min(cw2 / ratio, Math.max(0.5, ih - capH2));
|
|
582
|
+
var cy2 = iy + Math.max(0, (ih - ih2 - capH2) / 2);
|
|
583
|
+
[0, 1].forEach(function (i) {
|
|
584
|
+
var it = items[i] || {};
|
|
585
|
+
var cx2 = MX + i * (cw2 + cgap);
|
|
586
|
+
one(cx2, cy2, cw2, ih2, it, true);
|
|
587
|
+
var cap2 = it.caption || '';
|
|
588
|
+
if (cap2) sh.push(txSp(cx2, cy2 + ih2 + 0.02, cw2, 0.3,
|
|
589
|
+
[[{ t: cap2, sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
590
|
+
});
|
|
591
|
+
capY = cy2 + ih2 + capH2;
|
|
592
|
+
} else if (layout === 'wall' && items.length) {
|
|
593
|
+
var wgap = IM.wallGap || 0.18;
|
|
594
|
+
var wn = Math.min(items.length, 12);
|
|
595
|
+
var rowsW = Math.max(1, Math.min(IM.wallMaxRows || 2, Math.ceil(wn / 6)));
|
|
596
|
+
var perRow = Math.max(1, Math.ceil(wn / rowsW));
|
|
597
|
+
var cellW2 = (CW - (perRow - 1) * wgap) / perRow;
|
|
598
|
+
var cellH2 = Math.min(IM.wallCellH || 0.92, cellW2 / ratio,
|
|
599
|
+
Math.max(0.4, (ih - (rowsW - 1) * wgap) / rowsW));
|
|
600
|
+
var blockH2 = rowsW * cellH2 + (rowsW - 1) * wgap;
|
|
601
|
+
var wy = iy + Math.max(0, (ih - blockH2) / 2);
|
|
602
|
+
items.slice(0, wn).forEach(function (it, i) {
|
|
603
|
+
var cx3 = MX + (i % perRow) * (cellW2 + wgap);
|
|
604
|
+
var cy3 = wy + Math.floor(i / perRow) * (cellH2 + wgap);
|
|
605
|
+
one(cx3, cy3, cellW2, cellH2, it, true);
|
|
606
|
+
});
|
|
607
|
+
capY = wy + blockH2;
|
|
608
|
+
} else if (layout === 'half') {
|
|
609
|
+
var iw = CW * (IM.halfW || 0.56);
|
|
610
|
+
var ihh = Math.min(iw / ratio, ih);
|
|
611
|
+
one(MX, iy, iw, ihh, img, false);
|
|
612
|
+
var rxI = MX + iw + CW * (IM.halfGap || 0.06);
|
|
613
|
+
var rwI = CW - iw - CW * (IM.halfGap || 0.06);
|
|
614
|
+
var ptsI = points || [];
|
|
615
|
+
var rowHI = fitRowH(ih - 0.1, ptsI.length, IM.noteRowH || 0.62, 0.3);
|
|
616
|
+
var fzI = fitFont(ptsI.map(function (p) { return (p[0] || '') + (p[1] || ''); }), rwI - 0.3, ih,
|
|
617
|
+
{ max: 14, gapFactor: 0.6 });
|
|
618
|
+
ptsI.forEach(function (pt, i) {
|
|
619
|
+
var lyI = iy + i * rowHI;
|
|
620
|
+
sh.push(shape('rect', rxI, lyI + rowHI / 2 - 0.06, 0.12, 0.12, S.accent));
|
|
621
|
+
sh.push(txSp(rxI + 0.26, lyI, rwI - 0.3, rowHI - 0.04,
|
|
622
|
+
[[{ t: (pt[0] || '') + ' ', sz: fzI, b: 1, col: S.ink, font: S.font },
|
|
623
|
+
{ t: pt[1] || '', sz: fzI - 1, col: S.body, font: S.font }]], { anchor: 't' }));
|
|
624
|
+
});
|
|
625
|
+
capY = iy + Math.max(ihh, ih * 0.5);
|
|
626
|
+
} else if (layout === 'bleed') {
|
|
627
|
+
var bh = Math.min(ih, PW / ratio);
|
|
628
|
+
var by = iy + Math.max(0, (ih - bh) / 2);
|
|
629
|
+
one(0, by, PW, bh, img, false);
|
|
630
|
+
capY = by + bh;
|
|
631
|
+
} else {
|
|
632
|
+
var fh = Math.min(ih, CW / ratio);
|
|
633
|
+
var fy = iy + Math.max(0, (ih - fh) / 2);
|
|
634
|
+
one(MX, fy, CW, fh, img, false);
|
|
635
|
+
capY = fy + fh;
|
|
636
|
+
}
|
|
637
|
+
return capY;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/* ══ 图表形状渲染(预览/回归用近似版;交付通道 B 用原生可编辑数据图表) ══
|
|
641
|
+
A/B 两通道的「文本集合」必须一致(cross_verify 逐页比对):
|
|
642
|
+
类别标签 + 数值标签都渲染为文本,数值标签由 cross_verify 的数值 token 规则过滤。 */
|
|
643
|
+
function valText(v, unit) { return String(v) + (unit || ''); }
|
|
644
|
+
/* 横向条形(hbar / halftable / exhibit hbar) */
|
|
645
|
+
function hbarRows(sh, ec, S, y0, y1, dcols, x0, w) {
|
|
646
|
+
x0 = (x0 == null) ? MX : x0;
|
|
647
|
+
w = (w == null) ? CW : w;
|
|
648
|
+
var labels = ec.labels || [], values = ec.values || [];
|
|
649
|
+
var n = Math.max(1, labels.length);
|
|
650
|
+
var rowH = Math.min(PT.hbar.rowH, (y1 - y0) / n);
|
|
651
|
+
var barH = Math.max(0.16, rowH * (PT.hbar.barRatio || 0.5));
|
|
652
|
+
var labelW = PT.hbar.labelW, valW = PT.hbar.valW;
|
|
653
|
+
var trackX = x0 + labelW + 0.1, trackW = w - labelW - valW - 0.2;
|
|
654
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
655
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
656
|
+
labels.forEach(function (lb, i) {
|
|
657
|
+
var v = values[i] || 0;
|
|
658
|
+
var y = y0 + i * rowH + (rowH - barH) / 2;
|
|
659
|
+
var hot = v === top;
|
|
660
|
+
var dc = dcols ? dcols[i % 5] : null;
|
|
661
|
+
sh.push(txSp(x0, y - 0.06, labelW - 0.05, barH + 0.12,
|
|
662
|
+
[[{ t: lb, sz: 11, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
663
|
+
sh.push(shape('rect', trackX, y, trackW, barH, S.surface, { line: S.line }));
|
|
664
|
+
var bw2 = Math.max(0.02, v / vmax * trackW);
|
|
665
|
+
sh.push(shape('rect', trackX, y, bw2, barH, dc || (hot ? S.accent : S.faint)));
|
|
666
|
+
sh.push(txSp(trackX + trackW + 0.08, y - 0.06, valW, barH + 0.12,
|
|
667
|
+
[[{ t: valText(v, ec.unit), sz: 11.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/* 竖柱 / 堆叠柱 */
|
|
671
|
+
function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
672
|
+
var labels = ec.labels || [], values = ec.values || [];
|
|
673
|
+
var nb = Math.max(1, labels.length);
|
|
674
|
+
var baseY = y + h, plotW = w, step = plotW / nb;
|
|
675
|
+
var bw = Math.min(1.1, step * 0.55);
|
|
676
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
677
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
678
|
+
sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
|
|
679
|
+
values.forEach(function (v, i) {
|
|
680
|
+
var hh = Math.max(0.05, v / vmax * (h - 0.5));
|
|
681
|
+
var bx = x + i * step + (step - bw) / 2;
|
|
682
|
+
var hot = v === top;
|
|
683
|
+
var dc = dcols ? dcols[i % 5] : null;
|
|
684
|
+
sh.push(shape('roundRect', bx, baseY - hh, bw, hh, dc || (hot ? S.accent : S.faint), { adj: 6000 }));
|
|
685
|
+
sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
|
|
686
|
+
[[{ t: valText(v, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
|
|
687
|
+
sh.push(txSp(bx - 0.3, baseY + 0.08, bw + 0.6, 0.32,
|
|
688
|
+
[[{ t: labels[i], sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/* 折线 / 双折线 / 面积 */
|
|
692
|
+
function lineShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
693
|
+
var labels = ec.labels || [];
|
|
694
|
+
var series = (ec.series && ec.series.length) ? ec.series : [{ name: '数值', values: ec.values || [] }];
|
|
695
|
+
var n = Math.max(1, labels.length);
|
|
696
|
+
var all = [];
|
|
697
|
+
series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
|
|
698
|
+
var vmax = ec.max || Math.max.apply(null, all.concat([1]));
|
|
699
|
+
var plotH = h - 0.5, baseY = y + h;
|
|
700
|
+
sh.push(shape('rect', x, baseY, w, 0.02, S.line));
|
|
701
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
702
|
+
series.forEach(function (se, si) {
|
|
703
|
+
var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
|
|
704
|
+
var pts = (se.values || []).map(function (v, i) {
|
|
705
|
+
var px = x + (n > 1 ? i * stepX : w / 2);
|
|
706
|
+
var py = baseY - Math.max(0.02, (Number(v) || 0) / vmax * plotH);
|
|
707
|
+
return [px, py];
|
|
708
|
+
});
|
|
709
|
+
if (!pts.length) return;
|
|
710
|
+
for (var i = 0; i < pts.length - 1; i++) {
|
|
711
|
+
sh.push(lineSeg(pts[i][0], pts[i][1], pts[i + 1][0], pts[i + 1][1], col, 2.5));
|
|
712
|
+
}
|
|
713
|
+
pts.forEach(function (p) {
|
|
714
|
+
sh.push(shape('ellipse', p[0] - 0.055, p[1] - 0.055, 0.11, 0.11, col));
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
labels.forEach(function (lb, i) {
|
|
718
|
+
var px = x + (n > 1 ? i * stepX : w / 2);
|
|
719
|
+
sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
|
|
720
|
+
[[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
/* 直线段(用细长矩形近似,避免自定义几何——两通道渲染一致) */
|
|
724
|
+
function lineSeg(x1, y1, x2, y2, hex, thick) {
|
|
725
|
+
var dx = x2 - x1, dy = y2 - y1;
|
|
726
|
+
var len = Math.sqrt(dx * dx + dy * dy);
|
|
727
|
+
var ang = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
728
|
+
var cx = (x1 + x2) / 2, cy = (y1 + y2) / 2;
|
|
729
|
+
var t = thick / 72;
|
|
730
|
+
var rot = '<a:xfrm rot="' + Math.round(ang * 60000) + '">';
|
|
731
|
+
return '<p:sp><p:nvSpPr><p:cNvPr id="' + nid() + '" name="ln"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>' +
|
|
732
|
+
'<p:spPr>' + rot + '<a:off x="' + E(cx - len / 2) + '" y="' + E(cy - t / 2) + '"/>' +
|
|
733
|
+
'<a:ext cx="' + E(len) + '" cy="' + E(t) + '"/></a:xfrm>' +
|
|
734
|
+
'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>' + solid(hex) + '<a:ln><a:noFill/></a:ln></p:spPr>' +
|
|
735
|
+
'<p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody></p:sp>';
|
|
736
|
+
}
|
|
737
|
+
/* 散点 / 气泡(A 通道近似):只画点、不产文本——与 B 通道 XY 图表(类别为数值,过滤后为空)一致 */
|
|
738
|
+
function xyDots(sh, ec, S, x, y, w, h) {
|
|
739
|
+
var values = (ec.values || []).map(Number);
|
|
740
|
+
var pts = (ec.points && ec.points.length) ? ec.points : null;
|
|
741
|
+
var n = pts ? pts.length : Math.max(1, values.length);
|
|
742
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
743
|
+
var baseY = y + h, plotH = Math.max(0.3, h - 0.3);
|
|
744
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
745
|
+
sh.push(shape('rect', x, baseY, w, 0.02, S.line));
|
|
746
|
+
for (var i = 0; i < n; i++) {
|
|
747
|
+
var px = pts ? x + (Number(pts[i][0]) || 0) / vmax * w : x + i * stepX;
|
|
748
|
+
var py = pts ? baseY - (Number(pts[i][1]) || 0) / vmax * plotH : baseY - (values[i] || 0) / vmax * plotH;
|
|
749
|
+
sh.push(shape('ellipse', px - 0.07, py - 0.07, 0.14, 0.14, S.accent));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/* 仪表盘(A 通道近似):270° 弧段 + 数值(B 通道 gauge 的类别为数值,过滤后两端一致,故此处不产标签文本) */
|
|
753
|
+
function gaugeApprox(sh, ec, S, x, y, w, h) {
|
|
754
|
+
var v = Number((ec.values || [0])[0]) || 0;
|
|
755
|
+
var max = Number(ec.max) || 100;
|
|
756
|
+
var frac = Math.max(0, Math.min(1, v / max));
|
|
757
|
+
var r = Math.min(w, h) / 2 - 0.12;
|
|
758
|
+
var cx = x + w / 2, cy = y + h / 2;
|
|
759
|
+
var steps = 40, startA = Math.PI * 0.75, sweep = Math.PI * 1.5;
|
|
760
|
+
var px = null, py = null;
|
|
761
|
+
for (var i = 0; i <= steps; i++) {
|
|
762
|
+
var a = startA + sweep * (i / steps);
|
|
763
|
+
var qx = cx + r * Math.cos(a), qy = cy + r * Math.sin(a);
|
|
764
|
+
if (px !== null) sh.push(lineSeg(px, py, qx, qy, (i / steps) <= frac ? S.accent : S.line, 5));
|
|
765
|
+
px = qx; py = qy;
|
|
766
|
+
}
|
|
767
|
+
sh.push(txSp(x, cy - 0.2, w, 0.42,
|
|
768
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 18, b: 1, col: S.accent, font: S.fontDisplay }]], { align: 'ctr', anchor: 'ctr' }));
|
|
769
|
+
}
|
|
770
|
+
/* 饼 / 环(A 通道近似):横向占比条 + 图例(类别标签为文本,数值由 cross_verify 过滤) */
|
|
771
|
+
function pieApprox(sh, ec, S, x, y, w, h, dcols) {
|
|
772
|
+
var labels = ec.labels || [], values = (ec.values || []).map(Number);
|
|
773
|
+
var total = values.reduce(function (a, b) { return a + b; }, 0) || 1;
|
|
774
|
+
var barH = Math.max(0.32, Math.min(0.62, h * 0.22));
|
|
775
|
+
var barY = y + h / 2 - barH / 2 - 0.2;
|
|
776
|
+
var cx0 = x;
|
|
777
|
+
labels.forEach(function (lb, i) {
|
|
778
|
+
var bw = (values[i] || 0) / total * w;
|
|
779
|
+
sh.push(shape('rect', cx0, barY, bw, barH,
|
|
780
|
+
dcols ? dcols[i % dcols.length] : (i === 0 ? S.accent : S.faint)));
|
|
781
|
+
cx0 += bw;
|
|
782
|
+
});
|
|
783
|
+
var lx = x;
|
|
784
|
+
labels.forEach(function (lb, i) {
|
|
785
|
+
sh.push(shape('ellipse', lx, barY + barH + 0.16, 0.1, 0.1,
|
|
786
|
+
dcols ? dcols[i % dcols.length] : (i === 0 ? S.accent : S.faint)));
|
|
787
|
+
sh.push(txSp(lx + 0.14, barY + barH + 0.12, 1.2, 0.24, [[{ t: lb, sz: 10.5, col: S.body, font: S.font }]]));
|
|
788
|
+
sh.push(txSp(lx + 1.36, barY + barH + 0.12, 0.8, 0.24,
|
|
789
|
+
[[{ t: String(values[i] != null ? values[i] : '') + (ec.unit || ''), sz: 10.5, b: 1, col: S.body, font: S.font }]]));
|
|
790
|
+
lx += 2.3;
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
/* 迷你折线(A 近似):无轴无标签,仅趋势线 + 末值(与 B 的 shapeSpark 同文本集) */
|
|
794
|
+
function sparkApprox(sh, ec, S, x, y, w, h) {
|
|
795
|
+
var vals = (ec.values || []).map(Number);
|
|
796
|
+
var vmax = ec.max || Math.max.apply(null, vals.concat([1]));
|
|
797
|
+
var n = vals.length;
|
|
798
|
+
var stepX = n > 1 ? w / (n - 1) : 0;
|
|
799
|
+
var baseY = y + h - 0.06, plotH = Math.max(0.2, h - 0.18);
|
|
800
|
+
var px = 0, py = 0;
|
|
801
|
+
vals.forEach(function (v, i) {
|
|
802
|
+
var cx = x + i * stepX, cy = baseY - Math.max(0.02, (v / vmax) * plotH);
|
|
803
|
+
if (i > 0) sh.push(lineSeg(px, py, cx, cy, S.accent, 2));
|
|
804
|
+
px = cx; py = cy;
|
|
805
|
+
});
|
|
806
|
+
sh.push(txSp(x, y, w, 0.3, [[{ t: String(vals[n - 1] != null ? vals[n - 1] : '') + (ec.unit || ''),
|
|
807
|
+
sz: 11.5, b: 1, col: S.accent, font: S.font }]], { align: 'r' }));
|
|
808
|
+
}
|
|
809
|
+
/* K 线(A 近似):实体 + 须,标签 + 收盘值(与 B 的 shapeCandlestick 同文本集) */
|
|
810
|
+
function candleApprox(sh, ec, S, x, y, w, h) {
|
|
811
|
+
var labels = ec.labels || [];
|
|
812
|
+
var rows = (ec.values || []).filter(function (r) { return r && r.length >= 4; })
|
|
813
|
+
.map(function (r) { return r.slice(0, 4).map(Number); });
|
|
814
|
+
var n = Math.max(1, rows.length);
|
|
815
|
+
var flat = [];
|
|
816
|
+
rows.forEach(function (r) { r.forEach(function (v) { if (!isNaN(v)) flat.push(v); }); });
|
|
817
|
+
var lo = flat.length ? Math.min.apply(null, flat) : 0;
|
|
818
|
+
var hi = flat.length ? Math.max.apply(null, flat) : 1;
|
|
819
|
+
var span = (hi - lo) || 1;
|
|
820
|
+
var axisH = 0.26, plotH = Math.max(0.5, h - axisH - 0.1);
|
|
821
|
+
var yFor = function (v) { return y + 0.06 + (1 - (v - lo) / span) * plotH; };
|
|
822
|
+
var step = w / n, bw = Math.min(0.42, step * 0.5);
|
|
823
|
+
sh.push(shape('rect', x, y + 0.06 + plotH, w, 0.015, S.line));
|
|
824
|
+
rows.forEach(function (r, i) {
|
|
825
|
+
var o = r[0], hiP = r[1], loP = r[2], cl = r[3];
|
|
826
|
+
var cxp = x + step * i + step / 2;
|
|
827
|
+
var up = cl >= o;
|
|
828
|
+
sh.push(shape('rect', cxp - 0.011, yFor(hiP), 0.022, Math.max(0.02, yFor(loP) - yFor(hiP)), S.faint));
|
|
829
|
+
sh.push(shape('rect', cxp - bw / 2, yFor(Math.max(o, cl)), bw,
|
|
830
|
+
Math.max(0.04, Math.abs(yFor(cl) - yFor(o))), up ? S.accent : S.line));
|
|
831
|
+
if (labels[i]) {
|
|
832
|
+
sh.push(txSp(cxp - step / 2, y + plotH + axisH - 0.06, step, 0.24,
|
|
833
|
+
[[{ t: String(labels[i]), sz: 10, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
834
|
+
}
|
|
835
|
+
sh.push(txSp(cxp - step / 2, yFor(hiP) - 0.26, step, 0.24,
|
|
836
|
+
[[{ t: String(cl) + (ec.unit || ''), sz: 11.5, b: 1, col: up ? S.accent : S.body, font: S.font }]],
|
|
837
|
+
{ align: 'r' }));
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/* 通用形状近似(与 build_pptx.js 的 shapeProportion 同文本:标签 + 数值) */
|
|
841
|
+
function shapeProportion(sh, ec, S, x, y, w, h, dcols) {
|
|
842
|
+
var labels = ec.labels || [], values = (ec.values || []).map(Number);
|
|
843
|
+
var n = Math.max(1, labels.length);
|
|
844
|
+
var labelW = Math.min(2.4, w * 0.24), valW = 0.9;
|
|
845
|
+
var vmax = ec.max || Math.max.apply(null, values.concat([1]));
|
|
846
|
+
var top = values.length ? Math.max.apply(null, values) : 0;
|
|
847
|
+
var rowH = Math.max(0.22, Math.min(0.52, h / n));
|
|
848
|
+
var trackX = x + labelW + 0.08, trackW = w - labelW - valW - 0.2;
|
|
849
|
+
var barH = Math.max(0.12, rowH * 0.52);
|
|
850
|
+
labels.forEach(function (lb, i) {
|
|
851
|
+
var v = values[i] || 0, ry = y + i * rowH, hot = v === top;
|
|
852
|
+
sh.push(txSp(x, ry, labelW - 0.06, rowH, [[{ t: lb, sz: 11, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
853
|
+
sh.push(shape('rect', trackX, ry + (rowH - barH) / 2, trackW, barH, S.surface, { line: S.line }));
|
|
854
|
+
sh.push(shape('rect', trackX, ry + (rowH - barH) / 2, Math.max(0.02, Math.abs(v) / vmax * trackW), barH,
|
|
855
|
+
dcols ? dcols[i % dcols.length] : (hot ? S.accent : S.faint)));
|
|
856
|
+
sh.push(txSp(trackX + trackW + 0.06, ry, valW, rowH,
|
|
857
|
+
[[{ t: String(v) + (ec.unit || ''), sz: 11.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]],
|
|
858
|
+
{ align: 'right', anchor: 'ctr' }));
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
/* 数据表行(与 build_pptx.js 的 dataTableRows 同规则) */
|
|
862
|
+
function dataTableRows(ec) {
|
|
863
|
+
var labels = (ec.labels || []).map(String);
|
|
864
|
+
var unit = ec.unit ? '(' + ec.unit + ')' : '';
|
|
865
|
+
if (ec.series && ec.series.length) {
|
|
866
|
+
var out = [['项目'].concat(ec.series.map(function (se, i) { return se.name || ('系列' + (i + 1)); }))];
|
|
867
|
+
labels.forEach(function (lb, i) {
|
|
868
|
+
out.push([lb].concat(ec.series.map(function (se) {
|
|
869
|
+
var v = (se.values || [])[i]; return v == null ? '' : String(v);
|
|
870
|
+
})));
|
|
871
|
+
});
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
var out2 = [['项目', '数值' + unit]];
|
|
875
|
+
labels.forEach(function (lb, i) {
|
|
876
|
+
var v = (ec.values || [])[i];
|
|
877
|
+
out2.push([lb, v == null ? '' : String(v)]);
|
|
878
|
+
});
|
|
879
|
+
return out2;
|
|
880
|
+
}
|
|
881
|
+
/* 数据表策略(与 build_pptx.js 的 dataTableMode 同规则) */
|
|
882
|
+
function dataTableModeOf(ec) {
|
|
883
|
+
var t = (ec.type || 'bar').toLowerCase();
|
|
884
|
+
var reg = (typeof REG !== 'undefined' && REG && REG[t]) ? REG[t] : null;
|
|
885
|
+
var m = ec.dataTable || (reg && reg.dataTable) || 'notes';
|
|
886
|
+
return m === 'appendix' ? 'inline' : m;
|
|
887
|
+
}
|
|
888
|
+
/* 图表 + 数据表组合(与 build_pptx.js 的 chartBlock 同策略:inline 在图表下方附原生表格) */
|
|
889
|
+
function chartBlockShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
890
|
+
var dm = dataTableModeOf(ec);
|
|
891
|
+
var chH = h;
|
|
892
|
+
if (dm === 'inline' && (ec.labels || []).length) {
|
|
893
|
+
var rowsData = dataTableRows(ec);
|
|
894
|
+
var n = rowsData.length;
|
|
895
|
+
var tH = Math.min(h * 0.44, Math.max(0.44, n * 0.22 + 0.06));
|
|
896
|
+
chH = Math.max(0.85, h - tH - 0.1);
|
|
897
|
+
var colW = rowsData[0].map(function () { return w / rowsData[0].length; });
|
|
898
|
+
sh.push(table(x, y + chH + 0.1, w, colW, rowsData, S, { availH: tH, maxRowH: 0.24, minRowH: 0.14 }));
|
|
899
|
+
}
|
|
900
|
+
chartShapes(sh, ec, S, x, y, w, chH, dcols);
|
|
901
|
+
}
|
|
902
|
+
/* ══ 复杂信息图页型(A 通道形状近似:文本集合与 B 通道严格一致,cross_verify 逐页比对)══ */
|
|
903
|
+
function infoTableRows(sec) {
|
|
904
|
+
var t = sec.type || '';
|
|
905
|
+
var u = sec.unit ? '(' + sec.unit + ')' : '';
|
|
906
|
+
if (t === 'sankey') return [['源', '汇', '流量' + u]].concat((sec.flows || []).map(function (f) { return [String(f[0]), String(f[1]), String(f[2])]; }));
|
|
907
|
+
if (t === 'treemap') return [['项目', '数值' + u]].concat((sec.items || []).map(function (it) { return [String(it[0]), String(it[1])]; }));
|
|
908
|
+
if (t === 'boxplot') return [['分组', '最小', 'Q1', '中位', 'Q3', '最大']].concat((sec.groups || []).map(function (g) { return g.map(String); }));
|
|
909
|
+
if (t === 'network') return [['节点', '名称']].concat((sec.nodes || []).map(function (n) { return [String(n[0]), String(n[1])]; }));
|
|
910
|
+
if (t === 'marimekko') {
|
|
911
|
+
var head = ['列', '总量' + u].concat((sec.legend || []).map(String));
|
|
912
|
+
return [head].concat((sec.cols || []).map(function (c, i) {
|
|
913
|
+
return [String(c[0]), String(c[1])].concat(((sec.cells || [])[i] || []).map(String));
|
|
914
|
+
}));
|
|
915
|
+
}
|
|
916
|
+
if (t === 'streamgraph') {
|
|
917
|
+
return [['系列'].concat((sec.labels || []).map(String))]
|
|
918
|
+
.concat((sec.series || []).map(function (se) {
|
|
919
|
+
return [String(se.name || '')].concat((se.values || []).map(String));
|
|
920
|
+
}));
|
|
921
|
+
}
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
function infoDataTableMode(sec) {
|
|
925
|
+
var m = String(((sec.chart || {}).dataTable) || 'notes').toLowerCase();
|
|
926
|
+
return m === 'appendix' ? 'inline' : m;
|
|
927
|
+
}
|
|
928
|
+
function infoPlotA(sec, y0, y1) {
|
|
929
|
+
var rows = infoTableRows(sec);
|
|
930
|
+
var inline = (infoDataTableMode(sec) === 'inline') && rows && rows.length;
|
|
931
|
+
if (!inline) return { y0: y0, y1: y1, rows: rows };
|
|
932
|
+
var tH = Math.min((y1 - y0) * 0.4, Math.max(0.44, rows.length * 0.22 + 0.06));
|
|
933
|
+
return { y0: y0, y1: y1 - tH - 0.1, rows: rows, table: { y: y1 - tH, h: tH } };
|
|
934
|
+
}
|
|
935
|
+
function tmSplitA(items, x, y, w, h, out) {
|
|
936
|
+
if (!items.length) return;
|
|
937
|
+
if (items.length === 1) { out.push({ it: items[0], x: x, y: y, w: w, h: h }); return; }
|
|
938
|
+
var total = items.reduce(function (a, i) { return a + i.v; }, 0) || 1;
|
|
939
|
+
var acc = 0, k = 0;
|
|
940
|
+
for (; k < items.length - 1; k++) { acc += items[k].v; if (acc >= total / 2) break; }
|
|
941
|
+
var first = items.slice(0, k + 1), second = items.slice(k + 1);
|
|
942
|
+
var r = first.reduce(function (a, i) { return a + i.v; }, 0) / total;
|
|
943
|
+
if (w >= h) {
|
|
944
|
+
tmSplitA(first, x, y, w * r, h, out);
|
|
945
|
+
tmSplitA(second, x + w * r, y, w * (1 - r), h, out);
|
|
946
|
+
} else {
|
|
947
|
+
tmSplitA(first, x, y, w, h * r, out);
|
|
948
|
+
tmSplitA(second, x, y + h * r, w, h * (1 - r), out);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/* 信息图近似渲染:按页型画形状 + 落同样的标签文本 */
|
|
952
|
+
function infoApprox(sh, sec, S, y0, y1) {
|
|
953
|
+
var t = sec.type || '';
|
|
954
|
+
var P = infoPlotA(sec, y0, y1);
|
|
955
|
+
if (P.table) {
|
|
956
|
+
var colW = P.rows[0].map(function () { return CW / P.rows[0].length; });
|
|
957
|
+
sh.push(table(MX, P.table.y, CW, colW, P.rows, S, { availH: P.table.h, maxRowH: 0.24, minRowH: 0.15 }));
|
|
958
|
+
}
|
|
959
|
+
var palette = [S.accent, S.faint, S.body, S.line];
|
|
960
|
+
if (t === 'sankey') {
|
|
961
|
+
var flows = (sec.flows || []).filter(function (f) { return f && f.length >= 3; });
|
|
962
|
+
var nodes = [], idx = {};
|
|
963
|
+
flows.forEach(function (f) {
|
|
964
|
+
[f[0], f[1]].forEach(function (n) { if (idx[n] == null) { idx[n] = nodes.length; nodes.push(String(n)); } });
|
|
965
|
+
});
|
|
966
|
+
var level = nodes.map(function () { return 0; });
|
|
967
|
+
for (var it2 = 0; it2 < nodes.length + 1; it2++) {
|
|
968
|
+
var ch = false;
|
|
969
|
+
flows.forEach(function (f) {
|
|
970
|
+
var a = idx[f[0]], b = idx[f[1]];
|
|
971
|
+
if (level[b] < level[a] + 1) { level[b] = level[a] + 1; ch = true; }
|
|
972
|
+
});
|
|
973
|
+
if (!ch) break;
|
|
974
|
+
}
|
|
975
|
+
var maxLv = Math.max.apply(null, level.concat([0]));
|
|
976
|
+
var colW2 = CW / (maxLv + 1);
|
|
977
|
+
var geo = {};
|
|
978
|
+
for (var l = 0; l <= maxLv; l++) {
|
|
979
|
+
var lv = nodes.filter(function (n, i) { return level[i] === l; });
|
|
980
|
+
var rowH2 = Math.max(0.24, (P.y1 - P.y0) / Math.max(1, lv.length));
|
|
981
|
+
lv.forEach(function (n, k2) { geo[n] = { x: MX + l * colW2 + colW2 * 0.5 - 0.16, y: P.y0 + k2 * rowH2, h: rowH2 * 0.7 }; });
|
|
982
|
+
}
|
|
983
|
+
flows.forEach(function (f) {
|
|
984
|
+
var a = geo[f[0]], b = geo[f[1]];
|
|
985
|
+
if (!a || !b) return;
|
|
986
|
+
sh.push(lineSeg(a.x + 0.32, a.y + a.h / 2, b.x, b.y + b.h / 2, S.soft, 8));
|
|
987
|
+
/* 流量标签:与 B 通道同文本(数值 + 单位,由 cross_verify 过滤) */
|
|
988
|
+
sh.push(txSp((a.x + b.x) / 2 - 0.5, (a.y + b.y) / 2 - 0.14, 1.0, 0.28,
|
|
989
|
+
[[{ t: String(f[2]) + (sec.unit || ''), sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
990
|
+
});
|
|
991
|
+
nodes.forEach(function (n) {
|
|
992
|
+
var g = geo[n];
|
|
993
|
+
sh.push(shape('rect', g.x, g.y, 0.32, g.h, S.accent));
|
|
994
|
+
sh.push(txSp(g.x + 0.38, g.y, Math.min(1.4, colW2 * 0.5), g.h,
|
|
995
|
+
[[{ t: n, sz: 10.5, b: 1, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
996
|
+
});
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
if (t === 'treemap') {
|
|
1000
|
+
var items = (sec.items || []).map(function (it) { return { label: String(it[0]), v: Math.max(0, Number(it[1]) || 0) }; })
|
|
1001
|
+
.filter(function (i) { return i.v > 0; }).sort(function (a, b) { return b.v - a.v; });
|
|
1002
|
+
var out = [];
|
|
1003
|
+
tmSplitA(items, MX, P.y0, CW, P.y1 - P.y0, out);
|
|
1004
|
+
out.forEach(function (o) {
|
|
1005
|
+
sh.push(shape('rect', o.x + 0.03, o.y + 0.03, Math.max(0.05, o.w - 0.06), Math.max(0.05, o.h - 0.06), S.surface, { line: S.line }));
|
|
1006
|
+
sh.push(txSp(o.x + 0.1, o.y + 0.06, Math.max(0.2, o.w - 0.2), 0.28, [[{ t: o.it.label, sz: 10.5, b: 1, col: S.ink, font: S.font }]]));
|
|
1007
|
+
sh.push(txSp(o.x + 0.1, o.y + 0.32, Math.max(0.2, o.w - 0.2), 0.26,
|
|
1008
|
+
[[{ t: String(o.it.v) + (sec.unit || ''), sz: 10, col: S.body, font: S.font }]]));
|
|
1009
|
+
});
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (t === 'boxplot') {
|
|
1013
|
+
var groups = (sec.groups || []).filter(function (g) { return g && g.length >= 6; });
|
|
1014
|
+
var step = CW / Math.max(1, groups.length);
|
|
1015
|
+
var plotH = (P.y1 - P.y0) - 0.4;
|
|
1016
|
+
groups.forEach(function (g, i) {
|
|
1017
|
+
var cx = MX + step * i + step / 2;
|
|
1018
|
+
var bw = Math.min(0.9, step * 0.5);
|
|
1019
|
+
sh.push(shape('rect', cx - 0.012, P.y0, 0.024, plotH, S.faint));
|
|
1020
|
+
sh.push(shape('rect', cx - bw / 2, P.y0 + plotH * 0.2, bw, plotH * 0.6, S.soft, { line: S.accent }));
|
|
1021
|
+
sh.push(shape('rect', cx - bw / 2, P.y0 + plotH * 0.48, bw, 0.03, S.accent));
|
|
1022
|
+
sh.push(txSp(cx - step / 2, P.y1 - 0.34, step, 0.28,
|
|
1023
|
+
[[{ t: String(g[0]), sz: 10.5, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
1024
|
+
});
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (t === 'network') {
|
|
1028
|
+
var nds = sec.nodes || [];
|
|
1029
|
+
var n2 = Math.max(1, nds.length);
|
|
1030
|
+
var cx0 = MX + CW / 2, cy0 = (P.y0 + P.y1) / 2;
|
|
1031
|
+
var r = Math.min(CW / 2, (P.y1 - P.y0) / 2) - 1.0;
|
|
1032
|
+
var pos = {};
|
|
1033
|
+
nds.forEach(function (nd, i) {
|
|
1034
|
+
var a = -Math.PI / 2 + i * 2 * Math.PI / n2;
|
|
1035
|
+
pos[String(nd[0])] = { x: cx0 + r * Math.cos(a), y: cy0 + r * Math.sin(a) };
|
|
1036
|
+
});
|
|
1037
|
+
(sec.edges || []).forEach(function (e) {
|
|
1038
|
+
var a = pos[String(e[0])], b = pos[String(e[1])];
|
|
1039
|
+
if (!a || !b) return;
|
|
1040
|
+
sh.push(lineSeg(a.x, a.y, b.x, b.y, S.line, 1.5));
|
|
1041
|
+
});
|
|
1042
|
+
nds.forEach(function (nd) {
|
|
1043
|
+
var p = pos[String(nd[0])];
|
|
1044
|
+
sh.push(shape('ellipse', p.x - 0.26, p.y - 0.26, 0.52, 0.52, S.accent));
|
|
1045
|
+
var outward = p.x >= cx0;
|
|
1046
|
+
sh.push(txSp(outward ? p.x + 0.32 : p.x - 0.32 - 1.4, p.y - 0.14, 1.4, 0.28,
|
|
1047
|
+
[[{ t: String(nd[1] != null ? nd[1] : nd[0]), sz: 10, col: S.body, font: S.font }]],
|
|
1048
|
+
{ align: outward ? 'l' : 'r', anchor: 'ctr' }));
|
|
1049
|
+
});
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
if (t === 'marimekko') {
|
|
1053
|
+
var cols = sec.cols || [];
|
|
1054
|
+
var legend = (sec.legend || []).map(String);
|
|
1055
|
+
var total = cols.reduce(function (a, c) { return a + (Number(c[1]) || 0); }, 0) || 1;
|
|
1056
|
+
var plotH2 = (P.y1 - P.y0) - 0.6;
|
|
1057
|
+
var availW = CW - 0.06 * Math.max(0, cols.length - 1);
|
|
1058
|
+
var cx1 = MX;
|
|
1059
|
+
cols.forEach(function (c, i) {
|
|
1060
|
+
var cw = Math.max(0.2, (Number(c[1]) || 0) / total * availW);
|
|
1061
|
+
var shares = ((sec.cells || [])[i] || []).map(Number);
|
|
1062
|
+
var sum = shares.reduce(function (a, b) { return a + b; }, 0) || 1;
|
|
1063
|
+
var cy = P.y0;
|
|
1064
|
+
shares.forEach(function (v, k) {
|
|
1065
|
+
var shh = Math.max(0.02, v / sum * plotH2);
|
|
1066
|
+
sh.push(shape('rect', cx1, cy, cw, shh, palette[k % palette.length]));
|
|
1067
|
+
cy += shh;
|
|
1068
|
+
});
|
|
1069
|
+
sh.push(txSp(cx1, P.y0 + plotH2 + 0.06, cw, 0.28,
|
|
1070
|
+
[[{ t: String(c[0]), sz: 10.5, b: 1, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
1071
|
+
cx1 += cw + 0.06;
|
|
1072
|
+
});
|
|
1073
|
+
var lx = MX;
|
|
1074
|
+
legend.forEach(function (lg, k) {
|
|
1075
|
+
sh.push(shape('rect', lx, P.y1 - 0.2, 0.12, 0.12, palette[k % palette.length]));
|
|
1076
|
+
sh.push(txSp(lx + 0.16, P.y1 - 0.26, 1.5, 0.26, [[{ t: lg, sz: 10, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
1077
|
+
lx += 1.75;
|
|
1078
|
+
});
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (t === 'streamgraph') {
|
|
1082
|
+
var series = sec.series || [];
|
|
1083
|
+
var labels = sec.labels || [];
|
|
1084
|
+
var nPts = labels.length || ((series[0] && series[0].values || []).length);
|
|
1085
|
+
if (nPts >= 2) {
|
|
1086
|
+
var values = series.map(function (se) {
|
|
1087
|
+
return (se.values || []).map(function (v) { return Math.max(0, Number(v) || 0); });
|
|
1088
|
+
});
|
|
1089
|
+
var totals = [];
|
|
1090
|
+
for (var i2 = 0; i2 < nPts; i2++) {
|
|
1091
|
+
var tt = 0;
|
|
1092
|
+
values.forEach(function (v) { tt += (v[i2] || 0); });
|
|
1093
|
+
totals.push(tt);
|
|
1094
|
+
}
|
|
1095
|
+
var maxTot = Math.max.apply(null, totals.concat([1]));
|
|
1096
|
+
var plotH3 = P.y1 - P.y0;
|
|
1097
|
+
var cyMid = (P.y0 + P.y1) / 2;
|
|
1098
|
+
var base = totals.map(function (tv) { return cyMid - tv / maxTot * plotH3 / 2; });
|
|
1099
|
+
series.forEach(function (se, si) {
|
|
1100
|
+
var upper = base.map(function (bv, i3) { return bv + (values[si][i3] || 0) / maxTot * plotH3 / 2; });
|
|
1101
|
+
var stepX = CW / (nPts - 1);
|
|
1102
|
+
for (var i4 = 0; i4 < nPts - 1; i4++) {
|
|
1103
|
+
var hgt = Math.max(0.04, ((upper[i4] - base[i4]) + (upper[i4 + 1] - base[i4 + 1])) / 2);
|
|
1104
|
+
sh.push(shape('rect', MX + i4 * stepX, (base[i4] + base[i4 + 1]) / 2, stepX, hgt, palette[si % palette.length]));
|
|
1105
|
+
}
|
|
1106
|
+
base = upper;
|
|
1107
|
+
sh.push(shape('rect', MX, P.y1 + 0.06 + si * 0.26, 0.12, 0.12, palette[si % palette.length]));
|
|
1108
|
+
sh.push(txSp(MX + 0.16, P.y1 + si * 0.26, 1.5, 0.26,
|
|
1109
|
+
[[{ t: String(se.name || ('系列' + (si + 1))), sz: 10, col: S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
1110
|
+
});
|
|
1111
|
+
labels.forEach(function (lb, i5) {
|
|
1112
|
+
sh.push(txSp(MX + (nPts > 1 ? i5 / (nPts - 1) : 0.5) * CW - 0.4, P.y1 - 0.02, 0.8, 0.24,
|
|
1113
|
+
[[{ t: String(lb), sz: 9.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/* 统一图表分发(与 scripts/layout-constants.json 的 charts.registry 同分派规则)
|
|
1120
|
+
* A 通道为形状近似:类别标签与数值都落为文本,数值 token 由 cross_verify 过滤;
|
|
1121
|
+
* 复杂信息图(sankey / treemap / boxplot / network / marimekko / streamgraph)请用专属页型承载。 */
|
|
1122
|
+
function chartShapes(sh, ec, S, x, y, w, h, dcols) {
|
|
1123
|
+
var t = (ec.type || 'bar').toLowerCase();
|
|
1124
|
+
var reg = (typeof REG !== 'undefined' && REG && REG[t]) ? REG[t] : null;
|
|
1125
|
+
if (t === 'hbar') { hbarRows(sh, ec, S, y, y + h, dcols, x, w); return; }
|
|
1126
|
+
if (t === 'line' || t === 'dualline' || t === 'area') { lineShapes(sh, ec, S, x, y, w, h, dcols); return; }
|
|
1127
|
+
if (t === 'scatter' || t === 'bubble') { xyDots(sh, ec, S, x, y, w, h); return; }
|
|
1128
|
+
if (t === 'gauge') { gaugeApprox(sh, ec, S, x, y, w, h); return; }
|
|
1129
|
+
if (t === 'pie' || t === 'donut' || t === 'multidonut') { pieApprox(sh, ec, S, x, y, w, h, dcols); return; }
|
|
1130
|
+
/* sparkline / candlestick 的文本集须与 B 通道专属渲染器一致(否则 cross_verify 误报) */
|
|
1131
|
+
if (t === 'sparkline') { sparkApprox(sh, ec, S, x, y, w, h); return; }
|
|
1132
|
+
if (t === 'candlestick') { candleApprox(sh, ec, S, x, y, w, h); return; }
|
|
1133
|
+
if (reg && reg.pptx === 'shape') { shapeProportion(sh, ec, S, x, y, w, h, dcols); return; }
|
|
1134
|
+
vbarShapes(sh, ec, S, x, y, w, h, dcols);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/* 区域 IR 取用(与 scripts/lib_layout_regions.js 同源;未注入时回落 null) */
|
|
1138
|
+
function regOf(type, slot, opts) {
|
|
1139
|
+
return (typeof regionOf === 'function') ? regionOf(type, slot, opts) : null;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* ── 幻灯片生成(版式常量同 build_pptx.js · 同一 layout-constants.json) ── */
|
|
1143
|
+
function slidesOf(model, S) {
|
|
1144
|
+
var slides = [];
|
|
1145
|
+
_mode = model.mode || 'presentation';
|
|
1146
|
+
function head(sh, eyebrow, title, lead) {
|
|
1147
|
+
var H = (typeof regionOf === 'function') ? regionOf('head', 'head') : {
|
|
1148
|
+
x: MX, y: PT.common.headEyebrowY, w: CW,
|
|
1149
|
+
titleY: PT.common.headTitleY, ruleY: PT.common.headRuleY, leadY: PT.common.leadY,
|
|
1150
|
+
};
|
|
1151
|
+
if (eyebrow) sh.push(txSp(H.x, H.y, H.w, 0.4, [[{ t: eyebrow, sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
1152
|
+
var titleH = eyebrow ? (H.ruleY - H.titleY - 0.06) : 1.0;
|
|
1153
|
+
var tSize = estTextH(title, CW, 30, 1.25) > titleH ? 24 : 30;
|
|
1154
|
+
sh.push(txSp(H.x, eyebrow ? H.titleY : 0.6, H.w, Math.max(0.6, titleH), [[{ t: title, sz: tSize, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
1155
|
+
sh.push(shape('rect', H.x, eyebrow ? H.ruleY : 1.6, 0.9, 0.045, S.accent));
|
|
1156
|
+
if (lead) sh.push(txSp(H.x, H.leadY || PT.common.leadY, H.w, 0.5, [[{ t: lead, sz: 14, col: S.body, font: S.font }]]));
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/* 封面 */
|
|
1160
|
+
var CV = PT.cover;
|
|
1161
|
+
var c = [shape('rect', 0, 0, PW, PH, S.accent)];
|
|
1162
|
+
c.push(txSp(MX, CV.metaY, CW, 0.4, [[{ t: model.meta || '', sz: 12, col: S.soft, font: S.font }]]));
|
|
1163
|
+
var cTSize = estTextH(model.title, CW - 1, 44, 1.25) > CV.titleH ? 36 : 44;
|
|
1164
|
+
c.push(txSp(MX, CV.titleY, CW - 1, CV.titleH, [[{ t: model.title, sz: cTSize, b: 1, col: S.onAccent, font: S.fontDisplay }]]));
|
|
1165
|
+
c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
|
|
1166
|
+
slides.push(c);
|
|
1167
|
+
|
|
1168
|
+
/* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
|
|
1169
|
+
var items = model.agenda || [];
|
|
1170
|
+
if (items.length) {
|
|
1171
|
+
var ag = [];
|
|
1172
|
+
head(ag, 'AGENDA', '报告大纲');
|
|
1173
|
+
var colN = items.length > 8 ? 2 : 1;
|
|
1174
|
+
var perCol = Math.ceil(items.length / colN) || 1;
|
|
1175
|
+
var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP) / perCol);
|
|
1176
|
+
var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
|
|
1177
|
+
var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
|
|
1178
|
+
items.forEach(function (it, i) {
|
|
1179
|
+
var col = Math.floor(i / perCol), row = i % perCol;
|
|
1180
|
+
var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
|
|
1181
|
+
ag.push(txSp(x, y, 0.9, Math.min(0.9, rowH), [[{ t: it[0], sz: numSz, b: 1, col: S.accent, font: S.font }]]));
|
|
1182
|
+
ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
|
|
1183
|
+
[[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
|
|
1184
|
+
[{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
|
|
1185
|
+
});
|
|
1186
|
+
slides.push(ag);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/* 章节页 */
|
|
1190
|
+
(model.sections || []).forEach(function (sec) {
|
|
1191
|
+
var type = sec.type || 'points';
|
|
1192
|
+
var sh = [];
|
|
1193
|
+
if (type !== 'quote') head(sh, sec.eyebrow, sec.title, sec.lead);
|
|
1194
|
+
/* 非 exhibit 页型也可带 Exhibit 编号(帧头徽标;正文区下移让位,与 B 通道同规则) */
|
|
1195
|
+
var exBadge = (type !== 'exhibit' && sec.exhibitNo) ? String(sec.exhibitNo).toUpperCase() : null;
|
|
1196
|
+
var bodyY0 = sec.lead ? PT.common.bodyYWithLead : CONTENT_TOP;
|
|
1197
|
+
var bodyY = exBadge ? bodyY0 + 0.42 : bodyY0;
|
|
1198
|
+
if (exBadge) {
|
|
1199
|
+
sh.push(txSp(MX, CONTENT_TOP - 0.06, CW, 0.3,
|
|
1200
|
+
[[{ t: exBadge, sz: 11.5, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
1201
|
+
}
|
|
1202
|
+
var flagItems = Array.isArray(sec.flags) ? sec.flags.filter(Boolean) : [];
|
|
1203
|
+
var _F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
|
|
1204
|
+
var flagH = flagItems.length
|
|
1205
|
+
? (_F.hdH + Math.min(_F.maxRows, flagItems.length) * _F.rowH + 0.06) : 0;
|
|
1206
|
+
var bodyBottom = chartBottom(!!sec.soWhat, !!sec.footnote);
|
|
1207
|
+
var flagY = 0;
|
|
1208
|
+
if (flagH) {
|
|
1209
|
+
flagY = CONTENT_BOTTOM - flagH;
|
|
1210
|
+
if (sec.footnote) flagY = Math.min(flagY, PT.exhibit.footnoteY - flagH - 0.06);
|
|
1211
|
+
if (sec.soWhat) flagY = Math.min(flagY, PT.exhibit.soWhatY - flagH - 0.06);
|
|
1212
|
+
bodyBottom = Math.min(bodyBottom, flagY - (_F.gap || 0.12));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (type === 'metrics') {
|
|
1216
|
+
var M = PT.metrics, ms = sec.metrics || [], n = Math.max(1, ms.length);
|
|
1217
|
+
var mReg = regOf('metrics', 'primary') || { x: MX, y: M.bandY, w: CW, h: M.cardH };
|
|
1218
|
+
var g = cols(mReg.w, n, M.gap);
|
|
1219
|
+
var mw = Math.min(M.maxW, g.w);
|
|
1220
|
+
var gx = mReg.x + (mReg.w - n * mw - (n - 1) * M.gap) / 2;
|
|
1221
|
+
var cardH = Math.min(mReg.h, CONTENT_BOTTOM - mReg.y);
|
|
1222
|
+
ms.forEach(function (m, i) {
|
|
1223
|
+
var x = gx + i * (mw + M.gap);
|
|
1224
|
+
sh.push(shape('roundRect', x, mReg.y, mw, cardH, S.surface, { line: S.line, adj: 4000 }));
|
|
1225
|
+
var vSize = estTextH(m[0], mw - 0.2, 40, 1.1) > cardH * 0.5 ? 30 : 40;
|
|
1226
|
+
sh.push(txSp(x, M.valY, mw, M.valH, [[{ t: m[0], sz: vSize, b: 1, col: S.accent, font: S.fontDisplay }]], { align: 'ctr' }));
|
|
1227
|
+
sh.push(txSp(x + 0.15, M.capY, mw - 0.3, M.capH, [[{ t: m[1], sz: 11.5, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
1228
|
+
});
|
|
1229
|
+
} else if (type === 'kpi') {
|
|
1230
|
+
var K = PT.kpi, hro = sec.hero || [];
|
|
1231
|
+
var hReg = regOf('kpi', 'hero') || { x: MX, y: K.heroY, w: CW * K.heroW, h: K.heroH };
|
|
1232
|
+
sh.push(txSp(hReg.x, hReg.y, hReg.w, hReg.h, [[{ t: String(hro[0] || ''), sz: 64, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
1233
|
+
sh.push(txSp(hReg.x + 0.05, hReg.y + 1.5, hReg.w - 0.4, 0.55, [[{ t: String(hro[1] || ''), sz: 15, b: 1, col: S.ink, font: S.font }]]));
|
|
1234
|
+
if (hro[2]) sh.push(txSp(hReg.x + 0.05, hReg.y + 2.1, hReg.w - 0.4, 0.4, [[{ t: '▲ ' + hro[2], sz: 12, b: 1, col: S.accent, font: S.font }]]));
|
|
1235
|
+
var kdivX = MX + CW * K.dividerX;
|
|
1236
|
+
sh.push(shape('rect', kdivX, K.heroY + 0.15, 0.025, 3.1, S.line));
|
|
1237
|
+
var mRegK = regOf('kpi', 'metrics') || { x: kdivX + 0.35, y: K.metricY0, w: CW * (1 - K.dividerX) - 0.5 };
|
|
1238
|
+
var kmets = sec.metrics || [];
|
|
1239
|
+
var kCap = Math.max(1, Math.floor((CONTENT_BOTTOM - mRegK.y) / K.metricRowH));
|
|
1240
|
+
var kRows = Math.min(kmets.length, kCap);
|
|
1241
|
+
var kRowH = fitRowH(CONTENT_BOTTOM - mRegK.y, kRows, K.metricRowH, 0.42);
|
|
1242
|
+
var kTopH = Math.min(0.55, kRowH * 0.62);
|
|
1243
|
+
kmets.slice(0, kRows).forEach(function (m, i) {
|
|
1244
|
+
var ky = mRegK.y + i * kRowH;
|
|
1245
|
+
sh.push(txSp(mRegK.x, ky, mRegK.w, kTopH, [[{ t: m[0], sz: 22, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
1246
|
+
sh.push(txSp(mRegK.x, ky + kTopH, mRegK.w, Math.max(0.24, kRowH - kTopH), [[{ t: m[1], sz: 10.5, col: S.faint, font: S.font }]]));
|
|
1247
|
+
});
|
|
1248
|
+
} else if (type === 'comparison') {
|
|
1249
|
+
var C2 = PT.comparison;
|
|
1250
|
+
var lR = regOf('comparison', 'left') || { x: MX, y: C2.panelY, w: (CW - C2.panelGap) / 2, h: C2.panelH };
|
|
1251
|
+
var rR = regOf('comparison', 'right') || { x: MX + (CW + C2.panelGap) / 2, y: C2.panelY, w: (CW - C2.panelGap) / 2, h: C2.panelH };
|
|
1252
|
+
var cw2 = lR.w, cpy = lR.y, cph = lR.h, rx2 = rR.x;
|
|
1253
|
+
sh.push(shape('roundRect', lR.x, cpy, cw2, cph, S.surface, { adj: 4000, line: S.line }));
|
|
1254
|
+
sh.push(shape('roundRect', rx2, cpy, cw2, cph, S.soft, { adj: 4000 }));
|
|
1255
|
+
[{ x: lR.x, d: (sec.left || {}) }, { x: rx2, d: (sec.right || {}) }].forEach(function (sd) {
|
|
1256
|
+
var isRight = sd.x === rx2;
|
|
1257
|
+
var pts = sd.d.points || [];
|
|
1258
|
+
var avail = cph - C2.titleH - 0.34;
|
|
1259
|
+
var rowH2 = fitRowH(avail, pts.length, C2.rowH, 0.34);
|
|
1260
|
+
var fz = rowH2 < 0.5 ? 11.5 : 13;
|
|
1261
|
+
sh.push(txSp(sd.x + 0.22, cpy + 0.18, cw2 - 0.44, C2.titleH,
|
|
1262
|
+
[[{ t: sd.d.title || '', sz: 15, b: 1, col: isRight ? S.accent : S.ink, font: S.font }]]));
|
|
1263
|
+
pts.forEach(function (p, pi) {
|
|
1264
|
+
var py = cpy + 0.18 + C2.titleH + 0.16 + pi * rowH2;
|
|
1265
|
+
sh.push(shape('rect', sd.x + 0.24, py + rowH2 / 2 - 0.06, 0.12, 0.12, isRight ? S.accent : S.faint));
|
|
1266
|
+
sh.push(txSp(sd.x + 0.5, py, cw2 - 0.75, rowH2 - 0.04,
|
|
1267
|
+
[[{ t: p[0] + ' ', sz: fz, b: 1, col: S.ink, font: S.font },
|
|
1268
|
+
{ t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
if (sec.verdict) {
|
|
1272
|
+
var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
|
|
1273
|
+
sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
|
|
1274
|
+
sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
|
|
1275
|
+
[[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
|
|
1276
|
+
{ t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
|
|
1277
|
+
}
|
|
1278
|
+
} else if (type === 'quote') {
|
|
1279
|
+
var Q = PT.quote;
|
|
1280
|
+
var qR = regOf('quote', 'primary') || { x: MX + 1.05, y: Q.textY, w: CW - 2.1, h: Q.textH };
|
|
1281
|
+
/* 金句页用主题一致强调带(accent-soft 底)——浅色主题下不再出现深色页 */
|
|
1282
|
+
sh.push(shape('rect', 0, 0, PW, PH, S.soft));
|
|
1283
|
+
if (sec.eyebrow) sh.push(txSp(MX, PT.common.headEyebrowY, CW, 0.4, [[{ t: sec.eyebrow, sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
1284
|
+
sh.push(txSp(MX, PT.common.headTitleY, CW, 1.0, [[{ t: sec.title, sz: 30, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
1285
|
+
sh.push(shape('rect', MX, PT.common.headRuleY, 0.9, 0.045, S.accent));
|
|
1286
|
+
sh.push(txSp(MX + 0.05, Q.markY, 1.2, 1.1, [[{ t: '「', sz: 60, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
1287
|
+
var qSize = estTextH(sec.quote, qR.w, 20, 1.4) > qR.h ? 16 : 20;
|
|
1288
|
+
sh.push(txSp(qR.x, qR.y, qR.w, qR.h, [[{ t: sec.quote, sz: qSize, b: 1, col: S.ink, font: S.fontDisplay }]], { anchor: 'ctr' }));
|
|
1289
|
+
sh.push(txSp(qR.x, Q.authorY, qR.w, 0.45, [[{ t: '—— ' + sec.author, sz: 13, b: 1, col: S.accent, font: S.font }]], { align: 'r' }));
|
|
1290
|
+
if (sec.context) sh.push(txSp(qR.x, Q.contextY, qR.w, 0.4, [[{ t: sec.context, sz: 11, col: S.faint, font: S.font }]], { align: 'r' }));
|
|
1291
|
+
} else if (type === 'image') {
|
|
1292
|
+
/* 素材图片页:full 版心全宽 / half 左图右注 / bleed 通栏出血 / grid 多图网格 /
|
|
1293
|
+
compare 双图对比 / wall Logo 墙;无图(image.placeholder)走原生占位块 + 标签文本 */
|
|
1294
|
+
var IM = PT.image, img = sec.image || {};
|
|
1295
|
+
var items = Array.isArray(img.items) ? img.items.filter(Boolean) : [];
|
|
1296
|
+
var layout = String(img.layout || (items.length > 1 ? 'grid' : 'full')).toLowerCase();
|
|
1297
|
+
var fit = String(img.fit || ((typeof IMG !== 'undefined' && IMG && IMG.fitDefault) || 'cover')).toLowerCase();
|
|
1298
|
+
var isPh = !!img.placeholder;
|
|
1299
|
+
var iR = regOf('image', 'primary', { top: bodyY, caption: !!img.caption }) ||
|
|
1300
|
+
{ x: MX, y: Math.max(IM.y, bodyY), w: CW, h: IM.h };
|
|
1301
|
+
var iy = iR.y;
|
|
1302
|
+
var capH2 = img.caption ? 0.32 : 0;
|
|
1303
|
+
var ih = Math.max(1.2, iR.h);
|
|
1304
|
+
imageLayoutShapes(sh, S, layout, img, items, sec.points || [], isPh, fit, iy, iy + ih);
|
|
1305
|
+
if (img.caption) sh.push(txSp(MX, iy + ih + 0.04, CW, capH2, [[{ t: img.caption, sz: 10.5, col: S.faint, font: S.font }]]));
|
|
1306
|
+
} else if (type === 'donut') {
|
|
1307
|
+
var dcn = sec.chart || {};
|
|
1308
|
+
if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme));
|
|
1309
|
+
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
1310
|
+
} else if (type === 'heatmap') {
|
|
1311
|
+
var H = PT.heatmap;
|
|
1312
|
+
var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
|
|
1313
|
+
var rowsH = sec.rowHeads || [], colsH = sec.colHeads || [], cells = sec.cells || [];
|
|
1314
|
+
var nR = Math.max(1, rowsH.length), nC = Math.max(1, colsH.length);
|
|
1315
|
+
var availH = hR.h;
|
|
1316
|
+
var rowH3 = fitRowH(availH - H.headH, nR, H.cellH, 0.24);
|
|
1317
|
+
var gridW = hR.w - H.labelW;
|
|
1318
|
+
var cw3 = (gridW - (nC - 1) * H.cellGap) / nC;
|
|
1319
|
+
var flat = [], k2;
|
|
1320
|
+
for (k2 = 0; k2 < cells.length; k2++) for (var j2 = 0; j2 < (cells[k2] || []).length; j2++) { var nv = Number(cells[k2][j2]); if (!isNaN(nv)) flat.push(nv); }
|
|
1321
|
+
var vmax3 = flat.length ? Math.max.apply(null, flat) : 1;
|
|
1322
|
+
var vmin3 = flat.length ? Math.min.apply(null, flat) : 0;
|
|
1323
|
+
var levels = [S.surface, S.soft, S.line, S.accent];
|
|
1324
|
+
colsH.forEach(function (ch2, ci) {
|
|
1325
|
+
sh.push(txSp(hR.x + H.labelW + ci * (cw3 + H.cellGap), hR.y, cw3, H.headH,
|
|
1326
|
+
[[{ t: ch2, sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1327
|
+
});
|
|
1328
|
+
var rowsUse = Math.min(nR, cells.length);
|
|
1329
|
+
for (var ri = 0; ri < rowsUse; ri++) {
|
|
1330
|
+
var yy = hR.y + H.headH + 0.08 + ri * (rowH3 + H.cellGap);
|
|
1331
|
+
sh.push(txSp(hR.x, yy, H.labelW - 0.15, rowH3, [[{ t: rowsH[ri] || '', sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
1332
|
+
for (var ci2 = 0; ci2 < nC; ci2++) {
|
|
1333
|
+
var v2 = Number((cells[ri] || [])[ci2]);
|
|
1334
|
+
var has2 = !isNaN(v2);
|
|
1335
|
+
var p2 = (has2 && vmax3 > vmin3) ? (v2 - vmin3) / (vmax3 - vmin3) : 0.5;
|
|
1336
|
+
var lvl = p2 >= 0.86 ? 3 : (p2 >= 0.55 ? 2 : (p2 >= 0.25 ? 1 : 0));
|
|
1337
|
+
var cx2 = hR.x + H.labelW + ci2 * (cw3 + H.cellGap);
|
|
1338
|
+
sh.push(shape('roundRect', cx2, yy, cw3, rowH3, levels[lvl],
|
|
1339
|
+
{ adj: 3000, line: lvl === 0 ? S.line : undefined }));
|
|
1340
|
+
sh.push(txSp(cx2, yy, cw3, rowH3,
|
|
1341
|
+
[[{ t: has2 ? valText(v2, sec.unit) : '—', sz: 11.5, b: lvl >= 2 ? 1 : 0,
|
|
1342
|
+
col: lvl >= 3 ? S.onAccent : S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
var lg = sec.scaleLabel || ['低', '高'];
|
|
1346
|
+
var lgy = hR.y + H.headH + 0.08 + rowsUse * (rowH3 + H.cellGap) + 0.06;
|
|
1347
|
+
sh.push(txSp(hR.x, lgy, 0.6, 0.3, [[{ t: lg[0] || '低', sz: 9.5, col: S.faint, font: S.font }]]));
|
|
1348
|
+
for (var li2 = 0; li2 < 4; li2++) {
|
|
1349
|
+
sh.push(shape('rect', hR.x + 0.45 + li2 * 0.3, lgy + 0.04, 0.28, 0.16, levels[li2],
|
|
1350
|
+
{ line: li2 === 0 ? S.line : undefined }));
|
|
1351
|
+
}
|
|
1352
|
+
sh.push(txSp(hR.x + 1.75, lgy, 0.6, 0.3, [[{ t: lg[1] || '高', sz: 9.5, col: S.faint, font: S.font }]]));
|
|
1353
|
+
} else if (type === 'bullet') {
|
|
1354
|
+
var B = PT.bullet, its = sec.items || [], nB = Math.max(1, its.length);
|
|
1355
|
+
var bR = regOf('bullet', 'primary', { bottom: bodyBottom }) || { x: MX, y: B.y, w: CW, h: bodyBottom - B.y };
|
|
1356
|
+
var availB = bR.h;
|
|
1357
|
+
var rowHB = fitRowH(availB, nB, B.rowH, 0.26);
|
|
1358
|
+
var trackX = bR.x + B.labelW + 0.15;
|
|
1359
|
+
var trackW = bR.w - B.labelW - B.valW - 0.3;
|
|
1360
|
+
var nums = [], q2;
|
|
1361
|
+
for (q2 = 0; q2 < its.length; q2++) { var nq = Number(its[q2][1]); if (!isNaN(nq)) nums.push(nq); }
|
|
1362
|
+
var vmaxB = sec.max || (nums.length ? Math.max.apply(null, nums) : 1);
|
|
1363
|
+
its.forEach(function (it, i) {
|
|
1364
|
+
var yy2 = bR.y + i * rowHB + rowHB / 2 - B.barH / 2;
|
|
1365
|
+
var v = Number(it[1]) || 0, tgt = Number(it[2]);
|
|
1366
|
+
var hit = !isNaN(tgt) && v >= tgt;
|
|
1367
|
+
sh.push(txSp(bR.x, bR.y + i * rowHB, B.labelW, rowHB, [[{ t: String(it[0]), sz: 12, b: 1, col: S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
1368
|
+
sh.push(shape('roundRect', trackX, yy2, trackW, B.barH, S.surface, { adj: 50000, line: S.line }));
|
|
1369
|
+
var fw = Math.max(0.02, Math.min(1, v / (vmaxB || 1)) * trackW);
|
|
1370
|
+
sh.push(shape('roundRect', trackX, yy2, fw, B.barH, hit ? S.accent : S.faint, { adj: 50000 }));
|
|
1371
|
+
if (!isNaN(tgt)) {
|
|
1372
|
+
var tx2 = trackX + Math.min(1, tgt / (vmaxB || 1)) * trackW;
|
|
1373
|
+
sh.push(shape('rect', tx2 - 0.01, yy2 - 0.07, 0.02, B.barH + 0.14, S.body));
|
|
1374
|
+
}
|
|
1375
|
+
sh.push(txSp(trackX + trackW + 0.12, bR.y + i * rowHB, B.valW, rowHB,
|
|
1376
|
+
[[{ t: String(it[1]) + (sec.unit || ''), sz: 12, b: 1, col: hit ? S.accent : S.body, font: S.font },
|
|
1377
|
+
{ t: isNaN(tgt) ? '' : ' / ' + tgt + (sec.unit || ''), sz: 10, col: S.faint, font: S.font }]],
|
|
1378
|
+
{ align: 'r', anchor: 'ctr' }));
|
|
1379
|
+
});
|
|
1380
|
+
} else if (type === 'pyramid') {
|
|
1381
|
+
var P = PT.pyramid, lv = sec.levels || [], nP = Math.max(1, lv.length);
|
|
1382
|
+
var pR = regOf('pyramid', 'primary', { bottom: bodyBottom }) || { x: MX, y: P.y, w: CW, h: bodyBottom - P.y };
|
|
1383
|
+
var availP = pR.h;
|
|
1384
|
+
var rowHP = fitRowH(availP - (nP - 1) * P.gap, nP, P.rowH, 0.26);
|
|
1385
|
+
lv.forEach(function (l, i) {
|
|
1386
|
+
var frac = P.minW + (1 - P.minW) * (nP === 1 ? 1 : i / (nP - 1));
|
|
1387
|
+
var w2 = pR.w * frac, x2 = pR.x + (pR.w - w2) / 2, y2 = pR.y + i * (rowHP + P.gap);
|
|
1388
|
+
var acc = isRec(l) && l.accent;
|
|
1389
|
+
var lt = isRec(l) ? (l.t || '') : String(l[0] || '');
|
|
1390
|
+
var ld = isRec(l) ? (l.d || '') : String(l[1] || '');
|
|
1391
|
+
sh.push(shape('roundRect', x2, y2, w2, rowHP, acc ? S.soft : S.surface, { adj: 5000, line: acc ? undefined : S.line }));
|
|
1392
|
+
var lwP = Math.min(P.labelW, w2 - 0.44);
|
|
1393
|
+
sh.push(txSp(x2 + 0.22, y2, lwP, rowHP, [[{ t: lt, sz: 13.5, b: 1, col: acc ? S.accent : S.ink, font: S.font }]], { anchor: 'ctr' }));
|
|
1394
|
+
if (ld) sh.push(txSp(x2 + 0.22 + lwP, y2, w2 - 0.44 - lwP, rowHP, [[{ t: ld, sz: 11, col: S.faint, font: S.font }]], { anchor: 'ctr' }));
|
|
1395
|
+
});
|
|
1396
|
+
} else if (type === 'steps') {
|
|
1397
|
+
var S2 = PT.steps, st = sec.steps || [], maxPer = S2.maxPerRow || 6, groups = sec.groups || [];
|
|
1398
|
+
var sR = regOf('steps', 'primary') || { x: MX, y: S2.y, w: CW, h: S2.barH };
|
|
1399
|
+
var chunks = [];
|
|
1400
|
+
if (groups.length) {
|
|
1401
|
+
var kk = 0;
|
|
1402
|
+
groups.forEach(function (gp) { var cnt = Number(gp[1]) || 0; chunks.push({ label: gp[0], items: st.slice(kk, kk + cnt) }); kk += cnt; });
|
|
1403
|
+
if (kk < st.length) chunks.push({ label: '', items: st.slice(kk) });
|
|
1404
|
+
} else {
|
|
1405
|
+
for (var i3 = 0; i3 < st.length; i3 += maxPer) chunks.push({ label: '', items: st.slice(i3, i3 + maxPer) });
|
|
1406
|
+
}
|
|
1407
|
+
var rowsN = Math.max(1, chunks.length);
|
|
1408
|
+
var availS = bodyBottom - sR.y;
|
|
1409
|
+
var rowHS = fitRowH(availS - (rowsN - 1) * 0.3, rowsN, sR.h || S2.barH, 0.3);
|
|
1410
|
+
var cyS = sR.y;
|
|
1411
|
+
chunks.forEach(function (ck) {
|
|
1412
|
+
if (ck.label) {
|
|
1413
|
+
sh.push(txSp(sR.x, cyS - 0.02, sR.w, 0.26, [[{ t: ck.label, sz: 10, b: 1, col: S.faint, font: S.font, spc: 100 }]]));
|
|
1414
|
+
cyS += 0.28;
|
|
1415
|
+
}
|
|
1416
|
+
var n4 = Math.max(1, ck.items.length);
|
|
1417
|
+
var g4 = cols(sR.w, n4, S2.gap + S2.arrowW);
|
|
1418
|
+
ck.items.forEach(function (it, i) {
|
|
1419
|
+
var acc = isRec(it) && it.accent;
|
|
1420
|
+
var t4 = isRec(it) ? (it.t || '') : String(it[0] || '');
|
|
1421
|
+
var d4 = isRec(it) ? (it.d || '') : String(it[1] || '');
|
|
1422
|
+
var x4 = sR.x + i * g4.step;
|
|
1423
|
+
sh.push(shape('roundRect', x4, cyS, g4.w, rowHS, acc ? S.soft : S.surface, { adj: 5000, line: acc ? undefined : S.line }));
|
|
1424
|
+
var num = String(i + 1); if (num.length < 2) num = '0' + num;
|
|
1425
|
+
var pd = S2.pad, numH = S2.numH, ttlH = S2.titleH;
|
|
1426
|
+
sh.push(txSp(x4 + 0.14, cyS + pd, g4.w - 0.28, numH, [[{ t: num, sz: 10, b: 1, col: S.accent, font: S.font }]]));
|
|
1427
|
+
sh.push(txSp(x4 + 0.14, cyS + pd + numH, g4.w - 0.28, ttlH, [[{ t: t4, sz: 13, b: 1, col: acc ? S.accent : S.ink, font: S.font }]]));
|
|
1428
|
+
if (d4) sh.push(txSp(x4 + 0.14, cyS + pd + numH + ttlH, g4.w - 0.28, Math.max(0.2, rowHS - pd * 2 - numH - ttlH), [[{ t: d4, sz: 10, col: S.faint, font: S.font }]]));
|
|
1429
|
+
if (i < n4 - 1) {
|
|
1430
|
+
sh.push(txSp(x4 + g4.w, cyS, S2.arrowW + S2.gap, rowHS, [[{ t: '→', sz: 12, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
cyS += rowHS + 0.3;
|
|
1434
|
+
});
|
|
1435
|
+
} else if (type === 'table') {
|
|
1436
|
+
var tR = regOf('table', 'primary', { bottom: bodyBottom }) || { x: MX, y: CONTENT_TOP, w: CW, h: bodyBottom - CONTENT_TOP };
|
|
1437
|
+
var headRow = sec.table.head;
|
|
1438
|
+
var colW = sec.table.colW || headRow.map(function () { return tR.w / headRow.length; });
|
|
1439
|
+
sh.push(table(tR.x, tR.y, tR.w, colW, [headRow].concat(sec.table.rows), S, { availH: tR.h }));
|
|
1440
|
+
} else if (type === 'timeline') {
|
|
1441
|
+
var T = PT.timeline, ps = sec.phases || [], np = Math.max(1, ps.length);
|
|
1442
|
+
var tLR = regOf('timeline', 'primary') || { x: MX, y: T.labelY, w: CW };
|
|
1443
|
+
var stepW = (tLR.w - 1.0) / np;
|
|
1444
|
+
sh.push(shape('rect', tLR.x + 0.2, T.axisY, tLR.w - 0.4, 0.02, S.line));
|
|
1445
|
+
ps.forEach(function (p, i) {
|
|
1446
|
+
var x5 = tLR.x + 0.2 + i * stepW;
|
|
1447
|
+
var on = p[3] === 'done' || p[3] === 'now';
|
|
1448
|
+
sh.push(shape('ellipse', x5 - T.dotR / 2, T.dotY, T.dotR, T.dotR, on ? S.accent : S.line));
|
|
1449
|
+
sh.push(txSp(x5 - 0.1, T.labelY, stepW - 0.2, 0.4, [[{ t: p[0], sz: 12, b: 1, col: S.accent, font: S.font, spc: 100 }]]));
|
|
1450
|
+
sh.push(txSp(x5 - 0.1, T.nameY, stepW - 0.2, 0.5, [[{ t: p[1], sz: 17, b: 1, col: p[3] === 'now' ? S.accent : S.ink, font: S.fontDisplay }]]));
|
|
1451
|
+
sh.push(txSp(x5 - 0.1, T.descY, stepW - 0.25, T.descH, [[{ t: p[2] || '', sz: 12, col: S.body, font: S.font }]], { lineSpacing: 18 }));
|
|
1452
|
+
});
|
|
1453
|
+
} else if (type === 'bar') {
|
|
1454
|
+
var ch = sec.chart || {};
|
|
1455
|
+
var dcols2 = (ch.colors || dataColors(model.style, model.theme));
|
|
1456
|
+
var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
|
|
1457
|
+
var isHBar = ch.type === 'hbar';
|
|
1458
|
+
var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
|
|
1459
|
+
{ x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
|
|
1460
|
+
w: isHBar ? CW : CW - PT.bar.chartW, h: 3 };
|
|
1461
|
+
if (isHBar) {
|
|
1462
|
+
chartBlockShapes(sh, ch, S, bRR.x, bRR.y, bRR.w, bRR.h, dcols2);
|
|
1463
|
+
} else {
|
|
1464
|
+
chartBlockShapes(sh, ch, S, bRR.x, bRR.y, bRR.w, Math.max(1.5, bRR.h), dcols2);
|
|
1465
|
+
}
|
|
1466
|
+
if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
|
|
1467
|
+
} else if (type === 'twocol' || type === 'threecol') {
|
|
1468
|
+
var ps2 = sec.paragraphs || [];
|
|
1469
|
+
var nCol = (type === 'threecol') ? 3 : 2;
|
|
1470
|
+
var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
|
|
1471
|
+
var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
|
|
1472
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
1473
|
+
var gC = cols(tcR.w, nCol, gapC);
|
|
1474
|
+
var perC = Math.ceil(ps2.length / nCol);
|
|
1475
|
+
var availC = tcR.h;
|
|
1476
|
+
/* 探针与 build_pptx.js 同算法:按「各栏字数之和」的最大值估算(保证两通道字号一致) */
|
|
1477
|
+
var longest = 0, gi, pi2;
|
|
1478
|
+
for (gi = 0; gi < nCol; gi++) {
|
|
1479
|
+
var colG = ps2.slice(gi * perC, (gi + 1) * perC), ssum = 0;
|
|
1480
|
+
for (pi2 = 0; pi2 < colG.length; pi2++) ssum += String(colG[pi2][1] || '').length;
|
|
1481
|
+
if (ssum > longest) longest = ssum;
|
|
1482
|
+
}
|
|
1483
|
+
var probe = [], w4;
|
|
1484
|
+
for (w4 = 0; w4 < longest / 120; w4++) probe.push(new Array(121).join('x'));
|
|
1485
|
+
if (!probe.length) probe.push(new Array(1 + longest).join('x'));
|
|
1486
|
+
var fzC = fitFont(probe, gC.w, availC, { max: 13.5, gapFactor: 1.2 });
|
|
1487
|
+
for (var ci3 = 0; ci3 < nCol; ci3++) {
|
|
1488
|
+
var colC = ps2.slice(ci3 * perC, (ci3 + 1) * perC);
|
|
1489
|
+
if (!colC.length) continue;
|
|
1490
|
+
var runsC = [];
|
|
1491
|
+
colC.forEach(function (p) {
|
|
1492
|
+
runsC.push([{ t: p[0] + ' ', sz: fzC, b: 1, col: S.ink, font: S.font },
|
|
1493
|
+
{ t: p[1], sz: fzC - 1, col: S.body, font: S.font }]);
|
|
1494
|
+
});
|
|
1495
|
+
sh.push(txSp(tcR.x + ci3 * gC.step, tcR.y, gC.w, availC, runsC,
|
|
1496
|
+
{ lineSpacing: fzC * 1.5, spaceBefore: 7 }));
|
|
1497
|
+
}
|
|
1498
|
+
} else if (type === 'halftable') {
|
|
1499
|
+
var yH = sec.lead ? PT.common.bodyYWithLead : PT.research.denseTableY;
|
|
1500
|
+
var hLR = regOf('halftable', 'left', { top: yH, bottom: bodyBottom }) ||
|
|
1501
|
+
{ x: MX, y: yH, w: CW * PT.research.halfTableW, h: bodyBottom - yH };
|
|
1502
|
+
var hRR = regOf('halftable', 'right', { top: yH, bottom: bodyBottom }) ||
|
|
1503
|
+
{ x: MX + CW * PT.research.halfChartX, y: yH, w: CW * PT.research.halfChartW, h: bodyBottom - yH };
|
|
1504
|
+
var lwH = hLR.w;
|
|
1505
|
+
if (sec.table && sec.table.head) {
|
|
1506
|
+
var colWH = sec.table.colW || sec.table.head.map(function () { return lwH / sec.table.head.length; });
|
|
1507
|
+
sh.push(table(hLR.x, hLR.y, lwH, colWH, [sec.table.head].concat(sec.table.rows || []), S,
|
|
1508
|
+
{ availH: hLR.h, maxRowH: PT.research.denseRowH + 0.1, minRowH: 0.28 }));
|
|
1509
|
+
}
|
|
1510
|
+
var ecH = sec.chart || {};
|
|
1511
|
+
if (ecH.labels && ecH.values) {
|
|
1512
|
+
chartBlockShapes(sh, ecH, S, hRR.x, hRR.y, hRR.w, Math.max(1.2, hRR.h),
|
|
1513
|
+
(ecH.colors || dataColors(model.style, model.theme)));
|
|
1514
|
+
}
|
|
1515
|
+
} else if (type === 'matrix') {
|
|
1516
|
+
var rhH = sec.rowHeads || [], chH = sec.colHeads || [];
|
|
1517
|
+
var mxR = regOf('matrix', 'primary', { bottom: bodyBottom }) ||
|
|
1518
|
+
{ x: MX, y: PT.research.matrixY, w: CW, h: bodyBottom - PT.research.matrixY };
|
|
1519
|
+
var mY = mxR.y, mLW = PT.research.matrixLabelW;
|
|
1520
|
+
var rowsC = sec.cells || [], nR2 = Math.max(1, rowsC.length);
|
|
1521
|
+
var availM = mxR.h - 0.42;
|
|
1522
|
+
var mCH = fitRowH(availM, nR2, PT.research.matrixCellH, 0.3);
|
|
1523
|
+
var mW = (mxR.w - mLW) / Math.max(1, chH.length);
|
|
1524
|
+
chH.forEach(function (ch3, ci) {
|
|
1525
|
+
sh.push(txSp(mxR.x + mLW + ci * mW, mY, mW, 0.34, [[{ t: ch3, sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1526
|
+
});
|
|
1527
|
+
rowsC.forEach(function (rowC, ri) {
|
|
1528
|
+
var y6 = mY + 0.42 + ri * mCH;
|
|
1529
|
+
sh.push(txSp(mxR.x, y6 + (mCH - 0.5) / 2, mLW - 0.15, 0.5, [[{ t: rhH[ri] || '', sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'r', anchor: 'ctr' }));
|
|
1530
|
+
rowC.forEach(function (cell, ci) {
|
|
1531
|
+
var acc = isRec(cell) && cell.accent;
|
|
1532
|
+
var txt = isRec(cell) ? (cell.t || '') : String(cell);
|
|
1533
|
+
var cx4 = mxR.x + mLW + ci * mW;
|
|
1534
|
+
sh.push(shape('roundRect', cx4 + 0.04, y6, mW - 0.08, mCH - 0.1, acc ? S.soft : S.surface,
|
|
1535
|
+
{ adj: 3000, line: acc ? undefined : S.line }));
|
|
1536
|
+
sh.push(txSp(cx4 + 0.14, y6 + 0.08, mW - 0.28, mCH - 0.26, [[{ t: txt, sz: 11, col: acc ? S.accent : S.body, font: S.font }]], { anchor: 'ctr' }));
|
|
1537
|
+
});
|
|
1538
|
+
});
|
|
1539
|
+
} else if (type === 'lane') {
|
|
1540
|
+
var lnS = sec.lanes || [], nL = Math.max(1, lnS.length);
|
|
1541
|
+
var startL = sec.lead ? PT.common.bodyYWithLead : PT.arch.fullStartY;
|
|
1542
|
+
var lnR = regOf('lane', 'primary', { mode: model.mode, bottom: bodyBottom }) ||
|
|
1543
|
+
{ x: MX, y: startL, w: CW, h: bodyBottom - startL };
|
|
1544
|
+
var availL = lnR.h;
|
|
1545
|
+
var lnH = fitRowH(availL - (nL - 1) * PT.arch.laneGap, nL, PT.arch.laneH, 0.3);
|
|
1546
|
+
var lnGap = PT.arch.laneGap, lnHW = PT.arch.laneHeadW;
|
|
1547
|
+
lnS.forEach(function (ln, li) {
|
|
1548
|
+
var y7 = lnR.y + li * (lnH + lnGap);
|
|
1549
|
+
sh.push(shape('roundRect', lnR.x, y7, lnR.w, lnH, S.surface, { adj: 2500, line: S.line }));
|
|
1550
|
+
sh.push(shape('rect', lnR.x, y7, lnHW, lnH, S.surface, { line: S.line }));
|
|
1551
|
+
sh.push(txSp(lnR.x + 0.08, y7, lnHW - 0.16, lnH, [[{ t: ln[0], sz: 11, b: 1, col: S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1552
|
+
var steps = ln[1] || [];
|
|
1553
|
+
var stepW2 = Math.min(PT.arch.stepMaxW, (lnR.w - lnHW - 0.4 - (steps.length - 1) * PT.arch.stepGap) / Math.max(1, steps.length));
|
|
1554
|
+
steps.forEach(function (st, si) {
|
|
1555
|
+
var stAcc = isRec(st) && st.accent;
|
|
1556
|
+
var stT = isRec(st) ? st.t : st;
|
|
1557
|
+
var sx2 = lnR.x + lnHW + 0.2 + si * (stepW2 + PT.arch.stepGap);
|
|
1558
|
+
sh.push(shape('roundRect', sx2, y7 + 0.13, stepW2, lnH - 0.26, stAcc ? S.soft : S.bg,
|
|
1559
|
+
{ adj: 4000, line: stAcc ? undefined : S.line }));
|
|
1560
|
+
sh.push(txSp(sx2 + 0.06, y7 + 0.13, stepW2 - 0.12, lnH - 0.26,
|
|
1561
|
+
[[{ t: stT, sz: 11, b: stAcc ? 1 : 0, col: stAcc ? S.accent : S.ink, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1562
|
+
if (si < steps.length - 1) {
|
|
1563
|
+
sh.push(txSp(sx2 + stepW2, y7 + 0.13, PT.arch.stepGap, lnH - 0.26, [[{ t: '→', sz: 11, col: S.faint, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
});
|
|
1567
|
+
} else if (type === 'cards') {
|
|
1568
|
+
var cdC = PT.cards, cds = sec.cards || [];
|
|
1569
|
+
var cR = regOf('cards', 'primary', { bottom: bodyBottom }) ||
|
|
1570
|
+
{ x: MX, y: cdC.startY, w: CW, h: bodyBottom - cdC.startY };
|
|
1571
|
+
var cN = Math.min(sec.columns || 3, cds.length || 1);
|
|
1572
|
+
var rN = Math.ceil((cds.length || 1) / cN);
|
|
1573
|
+
var gw2 = (cR.w - (cN - 1) * cdC.gap) / cN;
|
|
1574
|
+
var gh2 = fitRowH(cR.h - (rN - 1) * cdC.gap, rN, cdC.maxH, 0.6);
|
|
1575
|
+
cds.forEach(function (cd, i) {
|
|
1576
|
+
var col2 = i % cN, row2 = Math.floor(i / cN);
|
|
1577
|
+
var x8 = cR.x + col2 * (gw2 + cdC.gap), y8 = cR.y + row2 * (gh2 + cdC.gap);
|
|
1578
|
+
sh.push(shape('roundRect', x8, y8, gw2, gh2, S.surface, { line: S.line, adj: 5000 }));
|
|
1579
|
+
/* 卡片头路标(与 B 通道同源:accent 小方块 = PPTX 侧图标等价物) */
|
|
1580
|
+
var ico2 = 0.18;
|
|
1581
|
+
sh.push(shape('roundRect', x8 + 0.18, y8 + 0.2, ico2, ico2, S.accent, { adj: 2500 }));
|
|
1582
|
+
sh.push(txSp(x8 + 0.18 + ico2 + 0.1, y8 + 0.14, gw2 - 0.36 - ico2 - 0.1, cdC.titleH,
|
|
1583
|
+
[[{ t: cd.title, sz: 15, b: 1, col: S.ink, font: S.font }]]));
|
|
1584
|
+
var paras2 = (cd.points || []).map(function (pt) {
|
|
1585
|
+
var line;
|
|
1586
|
+
if (pt && pt.length != null && typeof pt !== 'string') {
|
|
1587
|
+
var k = pt[0] == null ? '' : String(pt[0]);
|
|
1588
|
+
var v = pt[1] == null ? '' : String(pt[1]);
|
|
1589
|
+
line = (k && v) ? ('· ' + k + ' ' + v) : ('· ' + (k || v));
|
|
1590
|
+
} else if (pt && typeof pt === 'object') {
|
|
1591
|
+
line = '· ' + String(pt.t || '') + (pt.d ? ' ' + String(pt.d) : '');
|
|
1592
|
+
} else {
|
|
1593
|
+
line = '· ' + String(pt == null ? '' : pt);
|
|
1594
|
+
}
|
|
1595
|
+
return [{ t: line, sz: 12, col: S.body, font: S.font }];
|
|
1596
|
+
});
|
|
1597
|
+
sh.push(txSp(x8 + 0.18, y8 + 0.14 + cdC.titleH + 0.06, gw2 - 0.36, Math.max(0.4, gh2 - cdC.titleH - 0.34), paras2,
|
|
1598
|
+
{ lineSpacing: 18 }));
|
|
1599
|
+
});
|
|
1600
|
+
} else if (type === 'split') {
|
|
1601
|
+
/* 双区自由组合页:左区与右区各可为 要点 / 图表 / 表格 / 图片(与 B 通道同语义)。 */
|
|
1602
|
+
var SP = PT.split;
|
|
1603
|
+
var lRs = regOf('split', 'left', { bottom: bodyBottom }) ||
|
|
1604
|
+
{ x: MX, y: SP.chartY, w: CW * SP.leftW, h: bodyBottom - SP.chartY };
|
|
1605
|
+
var rRs = regOf('split', 'right', { bottom: bodyBottom }) ||
|
|
1606
|
+
{ x: MX + CW * SP.rightX, y: SP.chartY, w: CW * SP.rightW, h: bodyBottom - SP.chartY };
|
|
1607
|
+
var lw2 = lRs.w, rw2 = rRs.w, rxL = rRs.x;
|
|
1608
|
+
var lt2 = (sec.left && sec.left.type) || 'points';
|
|
1609
|
+
/* 要点区渲染(左右两区共用;与 B 通道同算法同文本) */
|
|
1610
|
+
var drawPointsL = function (el, x, w) {
|
|
1611
|
+
var ptsP = (el && el.points) || [];
|
|
1612
|
+
var lyP = bodyY, availP = bodyBottom - lyP;
|
|
1613
|
+
var rowHP = fitRowH(availP, ptsP.length, SP.rowH, 0.3);
|
|
1614
|
+
var probeP = [], q5;
|
|
1615
|
+
for (q5 = 0; q5 < ptsP.length; q5++) probeP.push(String(ptsP[q5][0] || '') + String(ptsP[q5][1] || ''));
|
|
1616
|
+
var fzP = fitFont(probeP, w - 0.4, availP, { max: 14, gapFactor: 0.6 });
|
|
1617
|
+
ptsP.forEach(function (pt, i) {
|
|
1618
|
+
var ly2 = lyP + i * rowHP;
|
|
1619
|
+
sh.push(shape('rect', x, ly2 + rowHP / 2 - 0.06, 0.12, 0.12, S.accent));
|
|
1620
|
+
sh.push(txSp(x + 0.28, ly2, w - 0.4, rowHP - 0.04,
|
|
1621
|
+
[[{ t: pt[0] + ' ', sz: fzP, b: 1, col: S.ink, font: S.font },
|
|
1622
|
+
{ t: pt[1], sz: fzP - 1, col: S.body, font: S.font }]]));
|
|
1623
|
+
});
|
|
1624
|
+
};
|
|
1625
|
+
if (lt2 === 'table') {
|
|
1626
|
+
var lhL = [sec.left.head].concat(sec.left.rows);
|
|
1627
|
+
var lcwL = sec.left.colW || sec.left.head.map(function () { return lw2 / sec.left.head.length; });
|
|
1628
|
+
sh.push(table(lRs.x, SP.tableY, lw2, lcwL, lhL, S, { availH: bodyBottom - SP.tableY, maxRowH: SP.tableRowH, minRowH: 0.3 }));
|
|
1629
|
+
} else if (lt2 === 'image') {
|
|
1630
|
+
var imL = (sec.left && sec.left.image) || {};
|
|
1631
|
+
addImageEl(sh, imL.src, S, lRs.x, SP.chartY, lw2, Math.max(1.4, bodyBottom - SP.chartY),
|
|
1632
|
+
{ placeholder: !!imL.placeholder || !imL.src, layout: 'half', multi: false, fit: imL.fit });
|
|
1633
|
+
if (imL.caption) sh.push(txSp(lRs.x, SP.capY, lw2, 0.35, [[{ t: imL.caption, sz: 11, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
1634
|
+
} else if (lt2 !== 'points') {
|
|
1635
|
+
var lc2 = sec.left;
|
|
1636
|
+
chartBlockShapes(sh, lc2, S, lRs.x, SP.chartY, lw2,
|
|
1637
|
+
Math.max(1.4, bodyBottom - SP.chartY - (lc2.cap ? 0.1 : 0)),
|
|
1638
|
+
lc2.colors || dataColors(model.style, model.theme));
|
|
1639
|
+
if (lc2.cap) sh.push(txSp(lRs.x, SP.capY, lw2, 0.35, [[{ t: lc2.cap, sz: 12, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
1640
|
+
} else {
|
|
1641
|
+
drawPointsL(sec.left, lRs.x, lw2);
|
|
1642
|
+
}
|
|
1643
|
+
var rt = (sec.right && sec.right.type) || 'bar';
|
|
1644
|
+
if (rt === 'table') {
|
|
1645
|
+
var rrL = [sec.right.head].concat(sec.right.rows);
|
|
1646
|
+
var rcwL = sec.right.colW || sec.right.head.map(function () { return rw2 / sec.right.head.length; });
|
|
1647
|
+
sh.push(table(rxL, SP.tableY, rw2, rcwL, rrL, S, { availH: bodyBottom - SP.tableY, maxRowH: SP.tableRowH, minRowH: 0.3 }));
|
|
1648
|
+
} else if (rt === 'image') {
|
|
1649
|
+
var im2 = (sec.right && sec.right.image) || {};
|
|
1650
|
+
addImageEl(sh, im2.src, S, rxL, SP.chartY, rw2, Math.max(1.4, bodyBottom - SP.chartY),
|
|
1651
|
+
{ placeholder: !!im2.placeholder || !im2.src, layout: 'half', multi: false });
|
|
1652
|
+
if (im2.caption) sh.push(txSp(rxL, SP.capY, rw2, 0.35, [[{ t: im2.caption, sz: 11, col: S.faint, font: S.font }]], { align: 'ctr' }));
|
|
1653
|
+
} else if (rt === 'points') {
|
|
1654
|
+
drawPointsL(sec.right, rxL, rw2);
|
|
1655
|
+
} else {
|
|
1656
|
+
var bc2 = sec.right;
|
|
1657
|
+
var dcols3 = bc2.colors || dataColors(model.style, model.theme);
|
|
1658
|
+
chartBlockShapes(sh, bc2, S, rxL, SP.chartY, rw2, Math.max(1.4, bodyBottom - SP.chartY - (bc2.cap ? 0.1 : 0)), dcols3);
|
|
1659
|
+
if (bc2.cap) sh.push(txSp(rxL, SP.capY, rw2, 0.35, [[{ t: bc2.cap, sz: 12, col: S.body, font: S.font }]], { align: 'ctr' }));
|
|
1660
|
+
}
|
|
1661
|
+
} else if (type === 'diagram') {
|
|
1662
|
+
var D = PT.diagram, lys = sec.layers || [];
|
|
1663
|
+
var dgR = regOf('diagram', 'primary', { mode: _mode, bottom: bodyBottom }) ||
|
|
1664
|
+
{ x: MX, y: D.bodyStartY, w: CW, h: bodyBottom - D.bodyStartY };
|
|
1665
|
+
var dStart = dgR.y;
|
|
1666
|
+
var dEnd = dgR.y + dgR.h;
|
|
1667
|
+
var nLay = Math.max(1, lys.length);
|
|
1668
|
+
var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
|
|
1669
|
+
lys.forEach(function (lay, li) {
|
|
1670
|
+
var y9 = dStart + li * (lh2 + D.layerGap);
|
|
1671
|
+
var focus = lay[2] === 'focus';
|
|
1672
|
+
sh.push(shape('roundRect', dgR.x, y9, D.layerBarW, lh2, focus ? S.accent : S.surface,
|
|
1673
|
+
{ adj: 3000, line: focus ? undefined : S.line }));
|
|
1674
|
+
sh.push(txSp(dgR.x + 0.1, y9 + 0.1, D.layerBarW - 0.2, lh2 - 0.2, [[{ t: lay[0], sz: 12, b: 1, col: focus ? S.onAccent : S.body, font: S.font }]], { align: 'ctr', anchor: 'ctr' }));
|
|
1675
|
+
var nodes = lay[1] || [], nn2 = Math.max(1, nodes.length);
|
|
1676
|
+
var availW = dgR.w - D.layerBarW - 0.2;
|
|
1677
|
+
var nw2 = Math.min(D.nodeMaxW, (availW - (nn2 - 1) * D.nodeGap) / nn2);
|
|
1678
|
+
nodes.forEach(function (nd, ni) {
|
|
1679
|
+
var x10 = dgR.x + D.layerBarW + 0.2 + ni * (nw2 + D.nodeGap);
|
|
1680
|
+
var ndacc = isRec(nd) ? nd.accent : false;
|
|
1681
|
+
var ntt = isRec(nd) ? nd.t : nd;
|
|
1682
|
+
var nds = isRec(nd) ? (nd.d || '') : '';
|
|
1683
|
+
sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
|
|
1684
|
+
sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
|
|
1685
|
+
[[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
|
|
1686
|
+
if (nds) sh.push(txSp(x10 + 0.12, y9 + lh2 - 0.5, nw2 - 0.24, 0.4, [[{ t: nds, sz: 10, col: S.faint, font: S.font }]]));
|
|
1687
|
+
});
|
|
1688
|
+
if (li < lys.length - 1) {
|
|
1689
|
+
var cy2 = y9 + lh2 + D.layerGap / 2;
|
|
1690
|
+
var cxm2 = dgR.x + D.layerBarW / 2;
|
|
1691
|
+
sh.push(shape('rect', cxm2 - 0.01, cy2 - 0.1, 0.02, 0.14, focus ? S.accent : S.line));
|
|
1692
|
+
sh.push(shape('rect', cxm2 - 0.06, cy2 + 0.04, 0.12, 0.02, focus ? S.accent : S.line));
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
if (sec.legend) {
|
|
1696
|
+
var lx2 = MX;
|
|
1697
|
+
sec.legend.forEach(function (lg2) {
|
|
1698
|
+
sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
|
|
1699
|
+
lx2 += 2.0;
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
} else if (type === 'exhibit') {
|
|
1703
|
+
var eReg = regOf('exhibit', 'primary')
|
|
1704
|
+
|| { x: MX, y: PT.exhibit.chartY, w: CW, badgeY: PT.exhibit.badgeY };
|
|
1705
|
+
if (sec.exhibitNo) {
|
|
1706
|
+
sh.push(txSp(MX, eReg.badgeY != null ? eReg.badgeY : PT.exhibit.badgeY, CW, 0.35, [[{ t: String(sec.exhibitNo).toUpperCase(), sz: 11.5, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
1707
|
+
}
|
|
1708
|
+
var ec2 = sec.chart || {};
|
|
1709
|
+
if (ec2.labels && ec2.values) {
|
|
1710
|
+
var edcols2 = (ec2.colors || dataColors(model.style, model.theme));
|
|
1711
|
+
var hh2 = Math.max(1.2, chartBottom(!!sec.soWhat, !!sec.footnote) - eReg.y - 0.05);
|
|
1712
|
+
if (ec2.type === 'hbar') {
|
|
1713
|
+
chartBlockShapes(sh, ec2, S, eReg.x, eReg.y, eReg.w, hh2, edcols2);
|
|
1714
|
+
} else {
|
|
1715
|
+
chartBlockShapes(sh, ec2, S, eReg.x + 0.4, eReg.y - 0.1, eReg.w - 0.8, hh2 + 0.15, edcols2);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
} else if (type === 'sankey' || type === 'treemap' || type === 'boxplot'
|
|
1719
|
+
|| type === 'network' || type === 'marimekko' || type === 'streamgraph') {
|
|
1720
|
+
var infoR = regOf(type, 'primary', { bottom: bodyBottom }) ||
|
|
1721
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
1722
|
+
infoApprox(sh, sec, S, infoR.y, infoR.y + infoR.h);
|
|
1723
|
+
} else {
|
|
1724
|
+
/* points(默认):要点列表 + 可选右侧指标列 */
|
|
1725
|
+
var P2 = PT.points, pts3 = sec.points || [];
|
|
1726
|
+
var hasM = !!(sec.metrics && sec.metrics.length);
|
|
1727
|
+
var ptsR = regOf('points', 'primary', { top: bodyY, bottom: bodyBottom }) ||
|
|
1728
|
+
{ x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
|
|
1729
|
+
/* 指标列最多 3 个 + 文本区严格分区(与 build_pptx.js 同算法) */
|
|
1730
|
+
var nm2 = hasM ? Math.min(sec.metrics.length, 3) : 0;
|
|
1731
|
+
var mw2 = hasM ? Math.min(P2.metricW, Math.max(1.6, (CW - 4.8) / nm2)) : 0;
|
|
1732
|
+
var textW = hasM ? Math.max(4.2, (PW - MX - nm2 * mw2) - MX - 0.35) : CW - 0.4;
|
|
1733
|
+
var availPt = ptsR.h;
|
|
1734
|
+
var rowHPt = fitRowH(availPt, pts3.length, P2.rowH, 0.3);
|
|
1735
|
+
var probeP = [], q5;
|
|
1736
|
+
for (q5 = 0; q5 < pts3.length; q5++) probeP.push(String(pts3[q5][0] || '') + String(pts3[q5][1] || ''));
|
|
1737
|
+
var fzP = fitFont(probeP, textW - 0.32, availPt, { max: 15, gapFactor: 0.55 });
|
|
1738
|
+
pts3.forEach(function (p, i) {
|
|
1739
|
+
var y11 = ptsR.y + i * rowHPt;
|
|
1740
|
+
sh.push(shape('rect', ptsR.x, y11 + rowHPt / 2 - P2.markSize / 2, P2.markSize, P2.markSize, S.accent));
|
|
1741
|
+
sh.push(txSp(ptsR.x + 0.32, y11, textW, rowHPt - 0.04,
|
|
1742
|
+
[[{ t: p[0] + ' ', sz: fzP, b: 1, col: S.ink, font: S.font },
|
|
1743
|
+
{ t: p[1], sz: fzP - 1, col: S.body, font: S.font }]]));
|
|
1744
|
+
});
|
|
1745
|
+
if (hasM) {
|
|
1746
|
+
var gx2 = PW - MX - nm2 * mw2;
|
|
1747
|
+
var vSz = nm2 >= 3 ? 28 : 34;
|
|
1748
|
+
sec.metrics.slice(0, nm2).forEach(function (m, i) {
|
|
1749
|
+
var x12 = gx2 + i * mw2;
|
|
1750
|
+
sh.push(txSp(x12, P2.metricY, mw2 - 0.2, P2.metricValH, [[{ t: m[0], sz: vSz, b: 1, col: S.accent, font: S.fontDisplay }]]));
|
|
1751
|
+
sh.push(txSp(x12, P2.metricCapY, mw2 - 0.2, 0.9, [[{ t: m[1], sz: 11, col: S.faint, font: S.font }]]));
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
|
|
1756
|
+
if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
|
|
1757
|
+
if (sec.footnote) footnoteLine(sh, sec.footnote, S);
|
|
1758
|
+
if (flagH) flagBar(sh, flagItems, S, flagY);
|
|
1759
|
+
slides.push(sh);
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
/* 收尾(主题一致强调带——accent-soft 底 + 常规文字,浅色模式不再出深色页) */
|
|
1763
|
+
var CL = PT.closing;
|
|
1764
|
+
var cl = [shape('rect', 0, 0, PW, PH, S.soft)];
|
|
1765
|
+
cl.push(shape('rect', MX, CL.eyebrowY, 0.9, 0.045, S.accent));
|
|
1766
|
+
cl.push(txSp(MX, CL.eyebrowY + 0.16, CW, 0.4, [[{ t: '下一步', sz: 13, b: 1, col: S.accent, font: S.font, spc: 200 }]]));
|
|
1767
|
+
cl.push(txSp(MX, CL.titleY, CW - 1, CL.titleH, [[{ t: model.closing.title, sz: 30, b: 1, col: S.ink, font: S.fontDisplay }]]));
|
|
1768
|
+
var clPts = model.closing.points || [], clN = Math.max(1, clPts.length);
|
|
1769
|
+
var clW = (CW - (clN - 1) * 0.3) / clN;
|
|
1770
|
+
clPts.forEach(function (p, i) {
|
|
1771
|
+
var x13 = MX + i * (clW + 0.3);
|
|
1772
|
+
cl.push(shape('roundRect', x13, CL.pointsY - 0.2, clW, CL.pointTitleH + CL.pointBodyH + 0.32, S.surface, { line: S.line, adj: 4000 }));
|
|
1773
|
+
cl.push(txSp(x13 + 0.18, CL.pointsY, clW - 0.36, CL.pointTitleH, [[{ t: p[0], sz: 17, b: 1, col: S.accent, font: S.font }]]));
|
|
1774
|
+
cl.push(txSp(x13 + 0.18, CL.pointsY + CL.pointTitleH, clW - 0.36, CL.pointBodyH, [[{ t: p[1], sz: 12.5, col: S.body, font: S.font }]]));
|
|
1775
|
+
});
|
|
1776
|
+
slides.push(cl);
|
|
1777
|
+
|
|
1778
|
+
/* 页码 */
|
|
1779
|
+
var total = slides.length;
|
|
1780
|
+
slides.forEach(function (sh, i) {
|
|
1781
|
+
sh.push(txSp(PW - 1.4, (LAY.pageNumY != null ? LAY.pageNumY : PH - 0.5), 0.9, 0.3,
|
|
1782
|
+
[[{ t: (i + 1) + ' / ' + total, sz: 10, col: S.faint, font: S.font }]], { align: 'r' }));
|
|
1783
|
+
});
|
|
1784
|
+
return slides;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/* ── 打包骨架 ── */
|
|
1788
|
+
var NS = {
|
|
1789
|
+
a: 'http://schemas.openxmlformats.org/drawingml/2006/main',
|
|
1790
|
+
p: 'http://schemas.openxmlformats.org/presentationml/2006/main',
|
|
1791
|
+
r: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
1792
|
+
};
|
|
1793
|
+
function slideXml(shapes) {
|
|
1794
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
|
1795
|
+
'<p:sld xmlns:a="' + NS.a + '" xmlns:p="' + NS.p + '" xmlns:r="' + NS.r + '">' +
|
|
1796
|
+
'<p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>' +
|
|
1797
|
+
'<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>' +
|
|
1798
|
+
shapes.join('') + '</p:spTree></p:cSld><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:sld>';
|
|
1799
|
+
}
|
|
1800
|
+
function relsXml(pairs) {
|
|
1801
|
+
var rs = pairs.map(function (p) {
|
|
1802
|
+
return '<Relationship Id="' + p[0] + '" Type="' + p[1] + '" Target="' + p[2] + '"/>';
|
|
1803
|
+
}).join('');
|
|
1804
|
+
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
|
|
1805
|
+
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' + rs + '</Relationships>';
|
|
1806
|
+
}
|
|
1807
|
+
var RT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
|
|
1808
|
+
|
|
1809
|
+
/* 每页 slide XML(预览模态用:与导出同一引擎,所见即所得)
|
|
1810
|
+
theme="dark" 时用 PRESETS_DARK(与页面深色主题同源),默认 light */
|
|
1811
|
+
function themePresets(theme) {
|
|
1812
|
+
return (theme === 'dark' && typeof PRESETS_DARK !== 'undefined' &&
|
|
1813
|
+
PRESETS_DARK && Object.keys(PRESETS_DARK).length) ? PRESETS_DARK : PRESETS;
|
|
1814
|
+
}
|
|
1815
|
+
function slidesXml(model) {
|
|
1816
|
+
var S = themePresets(model && model.theme)[model.style] || PRESETS['business-blue'];
|
|
1817
|
+
return slidesOf(model, S).map(slideXml);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/* ── 模型自检(schema 单源驱动:scripts/model-schema.json 经 sync_runtime 注入的 MODEL_SCHEMA;
|
|
1821
|
+
与 scripts/extract_model.py 本地校验同一份定义,双端不漂移) ── */
|
|
1822
|
+
function schemaGet(obj, path) {
|
|
1823
|
+
var cur = obj, ks = path.split('.');
|
|
1824
|
+
for (var i = 0; i < ks.length; i++) {
|
|
1825
|
+
if (cur == null || typeof cur !== 'object') return undefined;
|
|
1826
|
+
cur = cur[ks[i]];
|
|
1827
|
+
}
|
|
1828
|
+
return cur;
|
|
1829
|
+
}
|
|
1830
|
+
function schemaFieldOk(sec, spec) {
|
|
1831
|
+
/* 'anyof:a|b:c'(任一满足,用于 image 页型 src/items/placeholder 三选一);
|
|
1832
|
+
'a.b:array' / 'a.b:str' / 'a.b'(真值检查)——与 scripts/extract_model.py 同语义。 */
|
|
1833
|
+
if (spec.indexOf('anyof:') === 0) {
|
|
1834
|
+
return spec.slice(6).split('|').some(function (alt) { return alt && schemaFieldOk(sec, alt); });
|
|
1835
|
+
}
|
|
1836
|
+
var m = spec.match(/^(.*?)(?::(array|str))?$/), path = m[1], kind = m[2];
|
|
1837
|
+
var v = schemaGet(sec, path);
|
|
1838
|
+
if (kind === 'array') return Array.isArray(v) && v.length > 0;
|
|
1839
|
+
if (kind === 'str') return typeof v === 'string' && v.trim().length > 0;
|
|
1840
|
+
return !!v;
|
|
1841
|
+
}
|
|
1842
|
+
/* 图片模型软校验:版式 / 裁切策略 / 多图数量 / 外链 src——与 validate_pptx.py 硬门禁同口径,
|
|
1843
|
+
浏览器预览与 extract_model.py 都能提前看到问题。 */
|
|
1844
|
+
function imageModelWarnings(sec, where, warnings) {
|
|
1845
|
+
var IS = (typeof IMG !== 'undefined' && IMG) || {};
|
|
1846
|
+
var holders = [{ img: sec.image, at: where + '.image' }];
|
|
1847
|
+
if (sec.right && sec.right.image) holders.push({ img: sec.right.image, at: where + '.right.image' });
|
|
1848
|
+
holders.forEach(function (h) {
|
|
1849
|
+
var img = h.img;
|
|
1850
|
+
if (!img || typeof img !== 'object') return;
|
|
1851
|
+
var layouts = IS.layouts || ['full', 'half', 'bleed', 'grid', 'compare', 'wall'];
|
|
1852
|
+
var fitEnum = IS.fitEnum || ['cover', 'contain'];
|
|
1853
|
+
var items = Array.isArray(img.items) ? img.items.filter(Boolean) : [];
|
|
1854
|
+
var layout = String(img.layout || (items.length > 1 ? 'grid' : 'full')).toLowerCase();
|
|
1855
|
+
if (layouts.indexOf(layout) < 0) {
|
|
1856
|
+
warnings.push(h.at + '.layout="' + layout + '" 未知(应为 ' + layouts.join('/') + ')');
|
|
1857
|
+
}
|
|
1858
|
+
if (img.fit && fitEnum.indexOf(String(img.fit).toLowerCase()) < 0) {
|
|
1859
|
+
warnings.push(h.at + '.fit="' + img.fit + '" 未知(应为 ' + fitEnum.join('/') + ')');
|
|
1860
|
+
}
|
|
1861
|
+
if (img.placeholder && (img.src || items.length)) {
|
|
1862
|
+
warnings.push(h.at + ' 同时声明 placeholder 与图片源(placeholder 优先,图片将被忽略)');
|
|
1863
|
+
}
|
|
1864
|
+
if (items.length > (IS.maxPerPage || 6)) {
|
|
1865
|
+
warnings.push(h.at + '.items 共 ' + items.length + ' 张,超出单页上限 ' + (IS.maxPerPage || 6));
|
|
1866
|
+
}
|
|
1867
|
+
if (layout === 'compare' && items.length && items.length !== 2) {
|
|
1868
|
+
warnings.push(h.at + '.items 为 compare 版式时应为 2 张(当前 ' + items.length + ')');
|
|
1869
|
+
}
|
|
1870
|
+
[img].concat(items).forEach(function (it) {
|
|
1871
|
+
if (it && typeof it === 'object' && typeof it.src === 'string' &&
|
|
1872
|
+
/^\s*(?:https?:)?\/\//.test(it.src)) {
|
|
1873
|
+
warnings.push(h.at + ' 含外链 src(只允许 data: 内联或相对路径)');
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function validateModel(model) {
|
|
1880
|
+
var missing = [], warnings = [];
|
|
1881
|
+
var M = (typeof MODEL_SCHEMA !== 'undefined' && MODEL_SCHEMA) || {};
|
|
1882
|
+
var pageTypes = M.pageTypes || {};
|
|
1883
|
+
if (!model || typeof model !== 'object') {
|
|
1884
|
+
return { ok: false, missing: ['window.REPORT_MODEL(内容模型缺失)'], warnings: warnings, pages: 0 };
|
|
1885
|
+
}
|
|
1886
|
+
(M.model && M.model.required || ['title', 'sections:array', 'closing.title', 'closing.points:array'])
|
|
1887
|
+
.forEach(function (spec) {
|
|
1888
|
+
if (!schemaFieldOk(model, spec)) missing.push(spec.split(':')[0] + '(顶层必填)');
|
|
1889
|
+
});
|
|
1890
|
+
var mode = model.mode || 'presentation';
|
|
1891
|
+
var hasAgenda = Array.isArray(model.agenda) && model.agenda.length > 0;
|
|
1892
|
+
var agMin = (M.model && M.model.agendaMin && M.model.agendaMin[mode]) || 0;
|
|
1893
|
+
if (agMin > 0 && (!hasAgenda || model.agenda.length < agMin)) {
|
|
1894
|
+
missing.push('agenda(大纲 ≥' + agMin + ' 条)');
|
|
1895
|
+
}
|
|
1896
|
+
var secs = model.sections || [];
|
|
1897
|
+
if (!Array.isArray(secs) || secs.length < ((M.model && M.model.sectionsMin) || 1)) {
|
|
1898
|
+
missing.push('sections(章节页 ≥1)');
|
|
1899
|
+
}
|
|
1900
|
+
if (!model.closing || !model.closing.title) missing.push('closing(收尾页)');
|
|
1901
|
+
secs.forEach(function (sec, i) {
|
|
1902
|
+
var t = sec.type || 'points';
|
|
1903
|
+
var def = pageTypes[t];
|
|
1904
|
+
var where = 'sections[' + i + ']';
|
|
1905
|
+
if (!def) { missing.push(where + '.type="' + t + '"(未知页型)'); return; }
|
|
1906
|
+
if (!sec.title) missing.push(where + '.title(第 ' + (i + 1) + ' 章标题)');
|
|
1907
|
+
(def.required || []).forEach(function (spec) {
|
|
1908
|
+
if (!schemaFieldOk(sec, spec)) missing.push(where + '.' + spec + '(' + (def.label || t) + '必填)');
|
|
1909
|
+
});
|
|
1910
|
+
if (def.modes && def.modes.indexOf(mode) < 0) {
|
|
1911
|
+
warnings.push('sections[' + i + '] 页型 "' + t + '" 文档口径适用于 ' + def.modes.join('/') + ',当前 mode="' + mode + '"');
|
|
1912
|
+
}
|
|
1913
|
+
imageModelWarnings(sec, where, warnings);
|
|
1914
|
+
});
|
|
1915
|
+
if (M.modes && model.mode && M.modes.indexOf(model.mode) < 0) {
|
|
1916
|
+
warnings.push('mode="' + model.mode + '" 未知,按 presentation 处理');
|
|
1917
|
+
}
|
|
1918
|
+
if (model.style && !PRESETS[model.style]) warnings.push('style="' + model.style + '" 未知,回落商务蓝');
|
|
1919
|
+
if (model.theme && model.theme !== 'light' && model.theme !== 'dark') {
|
|
1920
|
+
warnings.push('theme="' + model.theme + '" 未知(应为 light/dark),按浅色导出');
|
|
1921
|
+
}
|
|
1922
|
+
if (secs.length < ((M.model && M.model.sectionsRecommended) || 3)) {
|
|
1923
|
+
warnings.push('章节页仅 ' + secs.length + ' 页,正式报告建议 ≥3');
|
|
1924
|
+
}
|
|
1925
|
+
var agMax = (M.model && M.model.agendaComfortMax) || 16;
|
|
1926
|
+
if ((model.agenda || []).length > agMax) {
|
|
1927
|
+
warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
|
|
1928
|
+
}
|
|
1929
|
+
var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
|
|
1930
|
+
return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
var api = {
|
|
1934
|
+
slidesXml: slidesXml,
|
|
1935
|
+
validateModel: validateModel,
|
|
1936
|
+
/* 以下供 scripts/gen_channel_a.js 在 Node 侧组装完整 PPTX 包(回归双裁判内部质量路径) */
|
|
1937
|
+
slidesOf: slidesOf, slideXml: slideXml, relsXml: relsXml, NS: NS, RT: RT, PRESETS: PRESETS,
|
|
1938
|
+
PRESETS_DARK: (typeof PRESETS_DARK !== 'undefined' ? PRESETS_DARK : null),
|
|
1939
|
+
dataColors: dataColors,
|
|
1940
|
+
esc: esc, solid: solid, E: E
|
|
1941
|
+
};
|
|
1942
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api; /* Node:gen_channel_a.js 用 */
|
|
1943
|
+
g.TopPptHtml = api;
|
|
1944
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|