foliko 1.1.93 → 2.0.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/.claude/settings.local.json +2 -1
- package/CLAUDE.md +56 -30
- package/REFACTORING_PLAN.md +645 -0
- package/docs/architecture.md +131 -0
- package/docs/migration.md +57 -0
- package/docs/public-api.md +138 -0
- package/docs/usage.md +385 -0
- package/examples/ambient-example.js +20 -137
- package/examples/basic.js +21 -48
- package/examples/bootstrap.js +16 -74
- package/examples/mcp-example.js +6 -29
- package/examples/skill-example.js +6 -19
- package/examples/workflow.js +8 -56
- package/package.json +8 -4
- package/plugins/README.md +49 -0
- package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
- package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
- package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
- package/plugins/ambient/README.md +14 -0
- package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
- package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
- package/plugins/{ambient-agent → ambient}/index.js +2 -2
- package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
- package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
- package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
- package/plugins/core/default/bootstrap.js +202 -0
- package/plugins/core/default/config.js +220 -0
- package/plugins/core/default/index.js +58 -0
- package/plugins/core/mcp/index.js +1 -0
- package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
- package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
- package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
- package/plugins/{session-plugin.js → core/session/index.js} +9 -73
- package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
- package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
- package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
- package/plugins/{think-plugin.js → core/think/index.js} +24 -91
- package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
- package/plugins/default-plugins.js +6 -720
- package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
- package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
- package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
- package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
- package/plugins/install/README.md +9 -0
- package/plugins/{install-plugin.js → install/index.js} +3 -3
- package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
- package/plugins/{web-plugin.js → io/web/index.js} +11 -113
- package/plugins/memory/README.md +13 -0
- package/plugins/{memory-plugin.js → memory/index.js} +4 -18
- package/plugins/messaging/email/README.md +19 -0
- package/plugins/{email → messaging/email}/index.js +2 -2
- package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
- package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
- package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
- package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
- package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
- package/plugins/{tools-plugin.js → tools/index.js} +68 -116
- package/plugins/trading/README.md +15 -0
- package/plugins/{gate-trading.js → trading/index.js} +8 -8
- package/{examples → sandbox}/test-concurrent-chat.js +2 -2
- package/{examples → sandbox}/test-long-chat.js +2 -2
- package/{examples → sandbox}/test-session-chat.js +2 -2
- package/{examples → sandbox}/test-web-plugin.js +1 -1
- package/{examples → sandbox}/test-weixin-feishu.js +2 -2
- package/src/agent/base.js +56 -0
- package/src/{core/agent-chat.js → agent/chat.js} +11 -11
- package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
- package/src/agent/index.js +111 -0
- package/src/agent/main.js +337 -0
- package/src/agent/prompt.js +78 -0
- package/src/agent/sub.js +198 -0
- package/src/agent/worker.js +104 -0
- package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
- package/{cli/src → src/cli}/commands/chat.js +25 -21
- package/{cli/src → src/cli}/index.js +1 -0
- package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
- package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
- package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
- package/src/common/errors.js +402 -0
- package/src/{utils → common}/logger.js +33 -0
- package/src/{utils/chat-queue.js → common/queue.js} +2 -2
- package/src/config/plugin-config.js +50 -0
- package/src/context/agent.js +32 -0
- package/src/context/compaction-prompts.js +170 -0
- package/src/context/compaction-utils.js +191 -0
- package/src/context/compressor.js +413 -0
- package/src/context/index.js +9 -0
- package/src/{core/context-manager.js → context/manager.js} +1 -1
- package/src/context/request.js +50 -0
- package/src/context/session.js +33 -0
- package/src/context/storage.js +30 -0
- package/src/executors/mcp-client.js +153 -0
- package/src/executors/mcp-desc.js +236 -0
- package/src/executors/mcp-executor.js +91 -956
- package/src/{core → framework}/command-registry.js +1 -1
- package/src/framework/framework.js +300 -0
- package/src/framework/index.js +18 -0
- package/src/framework/lifecycle.js +203 -0
- package/src/framework/loader.js +78 -0
- package/src/framework/registry.js +86 -0
- package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
- package/src/index.js +130 -15
- package/src/llm/index.js +26 -0
- package/src/llm/provider.js +212 -0
- package/src/llm/registry.js +11 -0
- package/src/{core/token-counter.js → llm/tokens.js} +4 -37
- package/src/{core/plugin-base.js → plugin/base.js} +10 -136
- package/src/plugin/index.js +14 -0
- package/src/plugin/loader.js +101 -0
- package/src/plugin/manager.js +261 -0
- package/src/{core → session}/branch-summary-auto.js +2 -2
- package/src/{core/chat-session.js → session/chat.js} +2 -2
- package/src/session/index.js +7 -0
- package/src/{core/session-manager.js → session/session.js} +2 -2
- package/src/session/ttl.js +92 -0
- package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
- package/src/tool/executor.js +85 -0
- package/src/tool/index.js +15 -0
- package/src/tool/registry.js +143 -0
- package/src/{core/tool-router.js → tool/router.js} +17 -124
- package/src/tool/schema.js +108 -0
- package/src/utils/data-splitter.js +1 -1
- package/src/utils/download.js +1 -1
- package/src/utils/index.js +6 -6
- package/src/utils/message-validator.js +1 -1
- package/tests/core/context-storage.test.js +46 -0
- package/tests/core/llm.test.js +54 -0
- package/tests/core/plugin.test.js +42 -0
- package/tests/core/tool.test.js +60 -0
- package/tests/setup.js +10 -0
- package/tests/smoke.test.js +58 -0
- package/vitest.config.js +9 -0
- package/cli/src/daemon.js +0 -149
- package/docs/CONTEXT_DESIGN.md +0 -1596
- package/docs/ai-sdk-optimization.md +0 -655
- package/docs/features.md +0 -120
- package/docs/qq-bot.md +0 -976
- package/docs/quick-reference.md +0 -160
- package/docs/user-manual.md +0 -1391
- package/images/geometric_shapes.jpg +0 -0
- package/images/sunset_mountain_lake.jpg +0 -0
- package/skills/poster-guide/SKILL.md +0 -792
- package/src/capabilities/index.js +0 -11
- package/src/core/agent.js +0 -808
- package/src/core/context-compressor.js +0 -959
- package/src/core/enhanced-context-compressor.js +0 -210
- package/src/core/framework.js +0 -1422
- package/src/core/index.js +0 -30
- package/src/core/plugin-manager.js +0 -961
- package/src/core/provider-registry.js +0 -159
- package/src/core/provider.js +0 -156
- package/src/core/request-context.js +0 -98
- package/src/core/subagent.js +0 -442
- package/src/core/system-prompt-builder.js +0 -120
- package/src/core/tool-executor.js +0 -202
- package/src/core/tool-registry.js +0 -517
- package/src/core/worker-agent.js +0 -192
- package/src/executors/executor-base.js +0 -58
- package/src/utils/error-boundary.js +0 -363
- package/src/utils/error.js +0 -374
- package/system.md +0 -1645
- package/website_v2/README.md +0 -57
- package/website_v2/SPEC.md +0 -1
- package/website_v2/docs/api.html +0 -128
- package/website_v2/docs/configuration.html +0 -147
- package/website_v2/docs/plugin-development.html +0 -129
- package/website_v2/docs/project-structure.html +0 -89
- package/website_v2/docs/skill-development.html +0 -85
- package/website_v2/index.html +0 -489
- package/website_v2/scripts/main.js +0 -93
- package/website_v2/styles/animations.css +0 -8
- package/website_v2/styles/docs.css +0 -83
- package/website_v2/styles/main.css +0 -417
- package/xhs_auth.json +0 -268
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
- /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/constants.js +0 -0
- /package/plugins/{email → messaging/email}/handlers.js +0 -0
- /package/plugins/{email → messaging/email}/monitor.js +0 -0
- /package/plugins/{email → messaging/email}/parser.js +0 -0
- /package/plugins/{email → messaging/email}/reply.js +0 -0
- /package/plugins/{email → messaging/email}/utils.js +0 -0
- /package/{examples → sandbox}/test-chat.js +0 -0
- /package/{examples → sandbox}/test-mcp.js +0 -0
- /package/{examples → sandbox}/test-reload.js +0 -0
- /package/{examples → sandbox}/test-telegram.js +0 -0
- /package/{examples → sandbox}/test-tg-bot.js +0 -0
- /package/{examples → sandbox}/test-tg-simple.js +0 -0
- /package/{examples → sandbox}/test-tg.js +0 -0
- /package/{examples → sandbox}/test-think.js +0 -0
- /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
- /package/{cli/src → src/cli}/commands/daemon.js +0 -0
- /package/{cli/src → src/cli}/commands/list.js +0 -0
- /package/{cli/src → src/cli}/commands/plugin.js +0 -0
- /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
- /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
- /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
- /package/{cli/src → src/cli}/utils/ansi.js +0 -0
- /package/{cli/src → src/cli}/utils/config.js +0 -0
- /package/{cli/src → src/cli}/utils/markdown.js +0 -0
- /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
- /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
- /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
- /package/src/{core → common}/constants.js +0 -0
- /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
- /package/src/{utils/event-emitter.js → common/events.js} +0 -0
- /package/src/{utils → common}/id.js +0 -0
- /package/src/{utils → common}/retry.js +0 -0
- /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
- /package/src/{core/session-entry.js → session/entry.js} +0 -0
- /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
# Poster Designer Plugin
|
|
2
|
-
|
|
3
|
-
画报/Banner 图绘制插件 - 基于 Node.js paper.js 实现,支持创建海报、宣传图、banner 图并导出为图片。
|
|
4
|
-
|
|
5
|
-
## 功能特性
|
|
6
|
-
|
|
7
|
-
### 预设尺寸
|
|
8
|
-
|
|
9
|
-
- **海报尺寸**: A4海报、方形海报、16:9海报
|
|
10
|
-
- **Banner尺寸**: 网站Banner、电商Banner、移动端Banner、小横幅
|
|
11
|
-
- **社交媒体**: Instagram、Facebook、Twitter 等平台尺寸
|
|
12
|
-
- **宣传图**: 多种常用宣传图尺寸
|
|
13
|
-
|
|
14
|
-
### 绘图功能
|
|
15
|
-
|
|
16
|
-
- 背景:纯色背景、线性渐变、径向渐变
|
|
17
|
-
- 图形:矩形、圆形/椭圆、线条、多边形
|
|
18
|
-
- 文字:普通文字、艺术文字(描边、渐变、阴影)
|
|
19
|
-
- 图片:添加图片、设为背景
|
|
20
|
-
- 圆角支持:矩形和图片都支持圆角裁剪
|
|
21
|
-
|
|
22
|
-
### 导出功能
|
|
23
|
-
|
|
24
|
-
- PNG 格式(默认)
|
|
25
|
-
- JPEG 格式(支持质量参数)
|
|
26
|
-
- Base64 编码导出
|
|
27
|
-
- 自动生成文件路径
|
|
28
|
-
|
|
29
|
-
## 安装
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm install poster-designer
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 使用方法
|
|
36
|
-
|
|
37
|
-
### 1. 创建画布
|
|
38
|
-
|
|
39
|
-
```javascript
|
|
40
|
-
// 使用预设尺寸
|
|
41
|
-
create_canvas({ preset: 'poster_square' });
|
|
42
|
-
|
|
43
|
-
// 自定义尺寸
|
|
44
|
-
create_canvas({ width: 1920, height: 1080, background: '#1a1a2e' });
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 2. 添加背景
|
|
48
|
-
|
|
49
|
-
```javascript
|
|
50
|
-
// 纯色背景
|
|
51
|
-
add_background({ color: '#1a1a2e' });
|
|
52
|
-
|
|
53
|
-
// 渐变背景
|
|
54
|
-
add_background({
|
|
55
|
-
gradient: {
|
|
56
|
-
type: 'linear',
|
|
57
|
-
colors: ['#ff6b6b', '#4ecdc4'],
|
|
58
|
-
direction: 45,
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 3. 添加元素
|
|
64
|
-
|
|
65
|
-
```javascript
|
|
66
|
-
// 矩形
|
|
67
|
-
add_rectangle({
|
|
68
|
-
x: 100,
|
|
69
|
-
y: 100,
|
|
70
|
-
width: 300,
|
|
71
|
-
height: 200,
|
|
72
|
-
fill: '#4ecdc4',
|
|
73
|
-
radius: 20,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 圆形
|
|
77
|
-
add_circle({
|
|
78
|
-
cx: 500,
|
|
79
|
-
cy: 500,
|
|
80
|
-
rx: 100,
|
|
81
|
-
fill: '#ff6b6b',
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// 文字
|
|
85
|
-
add_text({
|
|
86
|
-
text: 'Hello World',
|
|
87
|
-
x: 100,
|
|
88
|
-
y: 100,
|
|
89
|
-
fontSize: 48,
|
|
90
|
-
color: '#ffffff',
|
|
91
|
-
shadow: {
|
|
92
|
-
color: 'rgba(0,0,0,0.5)',
|
|
93
|
-
blur: 10,
|
|
94
|
-
offsetX: 2,
|
|
95
|
-
offsetY: 2,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 艺术文字
|
|
100
|
-
add_art_text({
|
|
101
|
-
text: 'FOLIKO',
|
|
102
|
-
x: 1000,
|
|
103
|
-
y: 1000,
|
|
104
|
-
fontSize: 120,
|
|
105
|
-
gradient: {
|
|
106
|
-
colors: ['#ff6b6b', '#feca57'],
|
|
107
|
-
direction: 45,
|
|
108
|
-
},
|
|
109
|
-
strokeColor: '#ffffff',
|
|
110
|
-
strokeWidth: 3,
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// 图片
|
|
114
|
-
add_image({
|
|
115
|
-
src: '/path/to/image.png',
|
|
116
|
-
x: 200,
|
|
117
|
-
y: 200,
|
|
118
|
-
width: 500,
|
|
119
|
-
height: 300,
|
|
120
|
-
rounded: 10,
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### 4. 导出
|
|
125
|
-
|
|
126
|
-
```javascript
|
|
127
|
-
// 导出为文件
|
|
128
|
-
export_canvas({
|
|
129
|
-
filename: 'my-poster',
|
|
130
|
-
format: 'png',
|
|
131
|
-
outputDir: '/app',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// 导出为 Base64
|
|
135
|
-
export_base64({ format: 'png' });
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 5. 一键生成海报
|
|
139
|
-
|
|
140
|
-
```javascript
|
|
141
|
-
// 使用预设模板快速生成
|
|
142
|
-
generate_poster({
|
|
143
|
-
template: 'modern', // simple, modern, business, social
|
|
144
|
-
title: 'FOLIKO AI',
|
|
145
|
-
subtitle: '智能助手新体验',
|
|
146
|
-
background: '#1a1a2e',
|
|
147
|
-
accentColor: '#e94560',
|
|
148
|
-
output: 'foliko-poster',
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## 可用工具
|
|
153
|
-
|
|
154
|
-
| 工具名称 | 描述 |
|
|
155
|
-
| ---------------------- | -------------------- |
|
|
156
|
-
| `list_presets` | 列出所有可用尺寸预设 |
|
|
157
|
-
| `create_canvas` | 创建新画布 |
|
|
158
|
-
| `add_background` | 添加背景色/渐变 |
|
|
159
|
-
| `add_rectangle` | 添加矩形 |
|
|
160
|
-
| `add_circle` | 添加圆形/椭圆 |
|
|
161
|
-
| `add_line` | 添加线条 |
|
|
162
|
-
| `add_polygon` | 绘制多边形 |
|
|
163
|
-
| `add_text` | 添加文字 |
|
|
164
|
-
| `add_art_text` | 添加艺术文字 |
|
|
165
|
-
| `add_image` | 添加图片 |
|
|
166
|
-
| `set_background_image` | 设置背景图片 |
|
|
167
|
-
| `export_canvas` | 导出为图片文件 |
|
|
168
|
-
| `export_base64` | 导出为 Base64 |
|
|
169
|
-
| `get_canvas_info` | 获取画布信息 |
|
|
170
|
-
| `clear_canvas` | 清除画布 |
|
|
171
|
-
| `generate_poster` | 一键生成海报 |
|
|
172
|
-
|
|
173
|
-
## 预设尺寸列表
|
|
174
|
-
|
|
175
|
-
### 海报
|
|
176
|
-
|
|
177
|
-
| Key | 尺寸 | 名称 |
|
|
178
|
-
| --------------- | --------- | --------------- |
|
|
179
|
-
| `poster_a4` | 2480×3508 | A4海报 (300dpi) |
|
|
180
|
-
| `poster_square` | 2000×2000 | 方形海报 |
|
|
181
|
-
| `poster_16_9` | 1920×1080 | 16:9海报 |
|
|
182
|
-
|
|
183
|
-
### Banner
|
|
184
|
-
|
|
185
|
-
| Key | 尺寸 | 名称 |
|
|
186
|
-
| ----------------- | -------- | ------------ |
|
|
187
|
-
| `banner_1920x500` | 1920×500 | 网站Banner |
|
|
188
|
-
| `banner_1200x400` | 1200×400 | 电商Banner |
|
|
189
|
-
| `banner_750x300` | 750×300 | 移动端Banner |
|
|
190
|
-
| `banner_468x60` | 468×60 | 小横幅 |
|
|
191
|
-
|
|
192
|
-
### 社交媒体
|
|
193
|
-
|
|
194
|
-
| Key | 尺寸 | 名称 |
|
|
195
|
-
| ------------------ | --------- | --------------- |
|
|
196
|
-
| `social_instagram` | 1080×1080 | Instagram正方形 |
|
|
197
|
-
| `social_story` | 1080×1920 | Instagram Story |
|
|
198
|
-
| `social_facebook` | 1200×630 | Facebook封面 |
|
|
199
|
-
| `social_twitter` | 1600×900 | Twitter封面 |
|
|
200
|
-
|
|
201
|
-
### 宣传图
|
|
202
|
-
|
|
203
|
-
| Key | 尺寸 | 名称 |
|
|
204
|
-
| --------------- | ------- | ---------- |
|
|
205
|
-
| `promo_900x500` | 900×500 | 宣传图 |
|
|
206
|
-
| `promo_500x500` | 500×500 | 正方宣传图 |
|
|
207
|
-
|
|
208
|
-
## 模板示例
|
|
209
|
-
|
|
210
|
-
### Modern(现代风格)
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
特点:几何装饰元素,大标题居中,渐变强调色
|
|
214
|
-
适用:科技产品、活动宣传
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Business(商务风格)
|
|
218
|
-
|
|
219
|
-
```
|
|
220
|
-
特点:简洁线条,专业配色,稳重排版
|
|
221
|
-
适用:企业宣传、商务推广
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Social(社交风格)
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
特点:圆形框架,居中头像效果,活力配色
|
|
228
|
-
适用:社交媒体头像、个人品牌
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Simple(简约风格)
|
|
232
|
-
|
|
233
|
-
```
|
|
234
|
-
特点:纯色背景,大留白,简洁文字
|
|
235
|
-
适用:极简主义、文艺风格
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
## 依赖
|
|
239
|
-
|
|
240
|
-
- `paper.js`: 图片处理
|
|
241
|
-
|
|
242
|
-
## 许可证
|
|
243
|
-
|
|
244
|
-
MIT License
|
|
245
|
-
|
|
246
|
-
paper.js nodejs使用的例子:
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
import paper from 'paper';
|
|
250
|
-
import path from 'path';
|
|
251
|
-
import { fileURLToPath } from 'url';
|
|
252
|
-
import fs from 'fs-extra';
|
|
253
|
-
import { createCanvas, registerFont } from 'canvas';
|
|
254
|
-
import { initDefaultFont, registerFontFile } from '../utils/font-manager.js';
|
|
255
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
256
|
-
const __dirname = path.dirname(__filename);
|
|
257
|
-
|
|
258
|
-
initDefaultFont();
|
|
259
|
-
/**
|
|
260
|
-
* 渲染器类 - 使用 Paper.js 渲染
|
|
261
|
-
*/
|
|
262
|
-
export class Renderer {
|
|
263
|
-
constructor(config = {}) {
|
|
264
|
-
this.width = config.width || 1920;
|
|
265
|
-
this.height = config.height || 1080;
|
|
266
|
-
this.fps = config.fps || 30;
|
|
267
|
-
this.quality = config.quality || 'high';
|
|
268
|
-
this.canvas = null;
|
|
269
|
-
this.project = null;
|
|
270
|
-
this.paper = null; // Paper.js 实例引用
|
|
271
|
-
this.initialized = false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* 初始化渲染器
|
|
276
|
-
*/
|
|
277
|
-
async init() {
|
|
278
|
-
if (this.initialized) return;
|
|
279
|
-
|
|
280
|
-
// 创建离屏 Canvas(用于最终输出)
|
|
281
|
-
this.canvas = paper.createCanvas(this.width, this.height);
|
|
282
|
-
|
|
283
|
-
// 创建新的 Paper.js Project(实例化)
|
|
284
|
-
// 每个 Renderer 都有自己独立的 project
|
|
285
|
-
this.project = new paper.Project(this.canvas);
|
|
286
|
-
|
|
287
|
-
// 保存 paper 引用(用于访问 Paper.js 的类,如 paper.Point, paper.Path 等)
|
|
288
|
-
this.paper = paper;
|
|
289
|
-
|
|
290
|
-
this.initialized = true;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* 获取 Paper.js 实例(用于传递给元素)
|
|
295
|
-
* @returns {Object} Paper.js 实例对象,包含 project 和 paper 类
|
|
296
|
-
*/
|
|
297
|
-
getPaperInstance() {
|
|
298
|
-
return {
|
|
299
|
-
project: this.project,
|
|
300
|
-
paper: this.paper, // 用于访问 Paper.js 的类(Point, Path, Color 等)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* 设置尺寸
|
|
306
|
-
*/
|
|
307
|
-
setSize(width, height) {
|
|
308
|
-
this.width = width;
|
|
309
|
-
this.height = height;
|
|
310
|
-
if (this.canvas) {
|
|
311
|
-
this.canvas.width = width;
|
|
312
|
-
this.canvas.height = height;
|
|
313
|
-
}
|
|
314
|
-
if (this.project && this.project.view) {
|
|
315
|
-
this.project.view.viewSize = new this.paper.Size(width, height);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* 清空场景
|
|
321
|
-
*/
|
|
322
|
-
clear() {
|
|
323
|
-
// 清空当前 project 的 activeLayer
|
|
324
|
-
if (this.project && this.project.activeLayer) {
|
|
325
|
-
this.project.activeLayer.removeChildren();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* 渲染一帧 - 使用 Paper.js
|
|
331
|
-
* @param {Array} layers - 图层数组
|
|
332
|
-
* @param {number} time - 当前时间(秒)
|
|
333
|
-
* @param {string} backgroundColor - 背景颜色
|
|
334
|
-
*/
|
|
335
|
-
async renderFrame(layers, time, backgroundColor = '#000000') {
|
|
336
|
-
if (!this.initialized) {
|
|
337
|
-
await this.init();
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// 先清空 canvas 的 2D context(确保 canvas 有初始状态)
|
|
341
|
-
const ctx = this.canvas.getContext('2d');
|
|
342
|
-
ctx.fillStyle = backgroundColor;
|
|
343
|
-
ctx.fillRect(0, 0, this.width, this.height);
|
|
344
|
-
|
|
345
|
-
// 清空 Paper.js 场景(复用 project 时只需要清空子元素)
|
|
346
|
-
if (this.project && this.project.activeLayer) {
|
|
347
|
-
this.project.activeLayer.removeChildren();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 绘制背景(使用 Paper.js)
|
|
351
|
-
// 只有当背景不是透明时才绘制
|
|
352
|
-
if (
|
|
353
|
-
backgroundColor &&
|
|
354
|
-
backgroundColor !== 'transparent' &&
|
|
355
|
-
backgroundColor !== 'rgba(0,0,0,0)'
|
|
356
|
-
) {
|
|
357
|
-
const bgRect = new this.paper.Path.Rectangle({
|
|
358
|
-
rectangle: new this.paper.Rectangle(0, 0, this.width, this.height),
|
|
359
|
-
fillColor: backgroundColor,
|
|
360
|
-
});
|
|
361
|
-
bgRect.sendToBack();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// 按 zIndex 排序图层
|
|
365
|
-
const sortedLayers = [...layers].sort((a, b) => a.zIndex - b.zIndex);
|
|
366
|
-
|
|
367
|
-
// 收集所有已渲染的元素
|
|
368
|
-
const renderedElements = [];
|
|
369
|
-
|
|
370
|
-
// 获取 Paper.js 实例(传递给图层和元素)
|
|
371
|
-
const paperInstance = this.getPaperInstance();
|
|
372
|
-
|
|
373
|
-
// 渲染所有图层到 Paper.js
|
|
374
|
-
for (let i = 0; i < sortedLayers.length; i++) {
|
|
375
|
-
const layer = sortedLayers[i];
|
|
376
|
-
if (layer.isActiveAtTime(time)) {
|
|
377
|
-
// 使用 Paper.js 渲染(支持异步)
|
|
378
|
-
// 传递 paperInstance 给图层,图层再传递给元素
|
|
379
|
-
const result = layer.render(this.project.activeLayer, time, paperInstance);
|
|
380
|
-
if (result && typeof result.then === 'function') {
|
|
381
|
-
await result;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// 收集图层中已渲染的元素
|
|
385
|
-
if (layer.getRenderedElements && typeof layer.getRenderedElements === 'function') {
|
|
386
|
-
const elements = layer.getRenderedElements();
|
|
387
|
-
renderedElements.push(...elements);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// 创建 onFrame 事件对象
|
|
393
|
-
const frameEvent = {
|
|
394
|
-
count: Math.floor(time * this.fps), // 帧计数
|
|
395
|
-
time: time, // 当前时间(秒)
|
|
396
|
-
delta: 1 / this.fps, // 帧间隔(秒)
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// 触发元素的 onFrame 回调
|
|
400
|
-
for (const element of renderedElements) {
|
|
401
|
-
if (element.onFrame) {
|
|
402
|
-
// 使用元素保存的 _paperItem 引用,并传递 paperInstance
|
|
403
|
-
element._callOnFrame(frameEvent, element._paperItem, paperInstance);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// 触发全局的 Paper.js onFrame 事件(如果已注册,用于向后兼容)
|
|
408
|
-
if (this.project.view && typeof this.project.view.onFrame === 'function') {
|
|
409
|
-
try {
|
|
410
|
-
this.project.view.onFrame(frameEvent);
|
|
411
|
-
} catch (e) {
|
|
412
|
-
console.warn('[Renderer] 全局 onFrame 事件触发失败:', e);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// 更新视图并绘制到 canvas
|
|
417
|
-
// Paper.js 在 Node.js 环境中需要调用 view.update() 来更新视图
|
|
418
|
-
// 然后使用 view.draw() 将内容绘制到 canvas
|
|
419
|
-
this.project.view.update();
|
|
420
|
-
|
|
421
|
-
// 检查 project 中的项目数量
|
|
422
|
-
const itemCount = this.project.activeLayer.children.length;
|
|
423
|
-
if (itemCount > 0) {
|
|
424
|
-
// 强制绘制
|
|
425
|
-
this.project.view.draw();
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return this.canvas;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* 将 BGRA 格式转换为 RGBA 格式
|
|
433
|
-
* @param {Buffer} bgraBuffer - BGRA 格式的 Buffer
|
|
434
|
-
* @returns {Buffer} RGBA 格式的 Buffer
|
|
435
|
-
*/
|
|
436
|
-
convertBGRAtoRGBA(bgraBuffer) {
|
|
437
|
-
const rgbaBuffer = Buffer.allocUnsafe(bgraBuffer.length);
|
|
438
|
-
// 每 4 个字节为一组:BGRA -> RGBA
|
|
439
|
-
for (let i = 0; i < bgraBuffer.length; i += 4) {
|
|
440
|
-
rgbaBuffer[i] = bgraBuffer[i + 2]; // R = B
|
|
441
|
-
rgbaBuffer[i + 1] = bgraBuffer[i + 1]; // G = G
|
|
442
|
-
rgbaBuffer[i + 2] = bgraBuffer[i]; // B = R
|
|
443
|
-
rgbaBuffer[i + 3] = bgraBuffer[i + 3]; // A = A
|
|
444
|
-
}
|
|
445
|
-
return rgbaBuffer;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* 获取 Canvas 缓冲区
|
|
450
|
-
* @param {string} format - 格式:'raw'(原始 RGBA 数据)或 'png'(PNG 图片)
|
|
451
|
-
* @returns {Buffer|null} Canvas 缓冲区
|
|
452
|
-
*/
|
|
453
|
-
getCanvasBuffer(format = 'raw') {
|
|
454
|
-
if (!this.canvas) return null;
|
|
455
|
-
|
|
456
|
-
if (format === 'raw') {
|
|
457
|
-
// 使用 toBuffer('raw') 获取原始数据,性能提升 5-10倍
|
|
458
|
-
// 注意:node-canvas 的 toBuffer('raw') 在不同平台上可能返回不同格式:
|
|
459
|
-
// - Windows: BGRA 格式
|
|
460
|
-
// - Linux/Mac: RGBA 格式
|
|
461
|
-
// FFmpeg 需要 RGBA 格式,所以如果是 BGRA 需要转换
|
|
462
|
-
let buffer = this.canvas.toBuffer('raw');
|
|
463
|
-
|
|
464
|
-
// 验证 buffer 大小是否正确
|
|
465
|
-
const expectedSize = this.width * this.height * 4;
|
|
466
|
-
if (buffer.length !== expectedSize) {
|
|
467
|
-
console.warn(
|
|
468
|
-
`[Renderer] Buffer size mismatch: expected ${expectedSize}, got ${buffer.length}`
|
|
469
|
-
);
|
|
470
|
-
// 如果大小不匹配,回退到 PNG 格式
|
|
471
|
-
return this.canvas.toBuffer('image/png');
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// 检测平台:Windows 上通常是 BGRA,需要转换为 RGBA
|
|
475
|
-
const platform = process.platform;
|
|
476
|
-
const isWindows = platform === 'win32';
|
|
477
|
-
|
|
478
|
-
if (isWindows) {
|
|
479
|
-
// Windows 上 toBuffer('raw') 返回 BGRA,需要转换为 RGBA
|
|
480
|
-
buffer = this.convertBGRAtoRGBA(buffer);
|
|
481
|
-
}
|
|
482
|
-
// Linux/Mac 上通常是 RGBA,不需要转换
|
|
483
|
-
|
|
484
|
-
return buffer;
|
|
485
|
-
} else {
|
|
486
|
-
// PNG 格式(用于保存帧文件或转场处理)
|
|
487
|
-
return this.canvas.toBuffer('image/png');
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* 获取 Canvas
|
|
493
|
-
*/
|
|
494
|
-
getCanvas() {
|
|
495
|
-
return this.canvas;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* 导出动画帧序列(类似 paper.view.exportFrames)
|
|
500
|
-
* @param {Array} layers - 图层数组
|
|
501
|
-
* @param {Object} options - 导出选项
|
|
502
|
-
* @param {number} options.amount - 要导出的总帧数
|
|
503
|
-
* @param {string} options.directory - 保存帧的目录
|
|
504
|
-
* @param {number} options.fps - 帧率(默认 30)
|
|
505
|
-
* @param {number} options.startTime - 开始时间(秒,默认 0)
|
|
506
|
-
* @param {number} options.endTime - 结束时间(秒)
|
|
507
|
-
* @param {string} options.backgroundColor - 背景颜色(默认 '#000000')
|
|
508
|
-
* @param {Function} options.onProgress - 进度回调函数 (current, total) => {}
|
|
509
|
-
* @param {Function} options.onComplete - 完成回调函数
|
|
510
|
-
* @returns {Promise<string[]>} 返回所有导出帧的文件路径数组
|
|
511
|
-
*/
|
|
512
|
-
async exportFrames(layers, options = {}) {
|
|
513
|
-
const {
|
|
514
|
-
amount,
|
|
515
|
-
directory = './frames',
|
|
516
|
-
fps = this.fps || 30,
|
|
517
|
-
startTime = 0,
|
|
518
|
-
endTime,
|
|
519
|
-
backgroundColor = '#000000',
|
|
520
|
-
onProgress,
|
|
521
|
-
onComplete,
|
|
522
|
-
} = options;
|
|
523
|
-
|
|
524
|
-
if (!this.initialized) {
|
|
525
|
-
await this.init();
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// 确保目录存在
|
|
529
|
-
await fs.ensureDir(directory);
|
|
530
|
-
|
|
531
|
-
// 计算实际帧数和时间范围
|
|
532
|
-
let totalFrames = amount;
|
|
533
|
-
let actualEndTime = endTime;
|
|
534
|
-
|
|
535
|
-
if (endTime !== undefined) {
|
|
536
|
-
// 如果指定了结束时间,根据帧率计算帧数
|
|
537
|
-
totalFrames = Math.ceil((endTime - startTime) * fps);
|
|
538
|
-
} else if (amount === undefined) {
|
|
539
|
-
// 如果都没有指定,使用默认值
|
|
540
|
-
totalFrames = 30; // 默认 1 秒
|
|
541
|
-
actualEndTime = startTime + 1;
|
|
542
|
-
} else {
|
|
543
|
-
// 如果只指定了帧数,计算结束时间
|
|
544
|
-
actualEndTime = startTime + totalFrames / fps;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const framePaths = [];
|
|
548
|
-
|
|
549
|
-
// 按 zIndex 排序图层
|
|
550
|
-
const sortedLayers = [...layers].sort((a, b) => a.zIndex - b.zIndex);
|
|
551
|
-
|
|
552
|
-
for (let frame = 0; frame < totalFrames; frame++) {
|
|
553
|
-
const time = startTime + frame / fps;
|
|
554
|
-
const frameNumber = frame + 1;
|
|
555
|
-
|
|
556
|
-
// 渲染帧
|
|
557
|
-
await this.renderFrame(sortedLayers, time, backgroundColor);
|
|
558
|
-
|
|
559
|
-
// 保存帧
|
|
560
|
-
const framePath = path.join(
|
|
561
|
-
directory,
|
|
562
|
-
`frame_${frameNumber.toString().padStart(4, '0')}.png`
|
|
563
|
-
);
|
|
564
|
-
const buffer = this.getCanvasBuffer();
|
|
565
|
-
await fs.writeFile(framePath, buffer);
|
|
566
|
-
framePaths.push(framePath);
|
|
567
|
-
|
|
568
|
-
// 调用进度回调
|
|
569
|
-
if (onProgress && typeof onProgress === 'function') {
|
|
570
|
-
onProgress(frameNumber, totalFrames);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// 调用完成回调
|
|
575
|
-
if (onComplete && typeof onComplete === 'function') {
|
|
576
|
-
onComplete(framePaths);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return framePaths;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
* 销毁渲染器
|
|
584
|
-
*/
|
|
585
|
-
destroy() {
|
|
586
|
-
// 清理 Paper.js 项目
|
|
587
|
-
if (this.project) {
|
|
588
|
-
try {
|
|
589
|
-
this.project.clear();
|
|
590
|
-
// 移除所有图层和项目
|
|
591
|
-
if (this.project.activeLayer) {
|
|
592
|
-
this.project.activeLayer.removeChildren();
|
|
593
|
-
}
|
|
594
|
-
// 移除项目
|
|
595
|
-
if (this.project && this.project.remove) {
|
|
596
|
-
this.project.remove();
|
|
597
|
-
}
|
|
598
|
-
} catch (error) {
|
|
599
|
-
// 忽略清理错误
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// 清理 Canvas
|
|
604
|
-
if (this.canvas) {
|
|
605
|
-
try {
|
|
606
|
-
// 清空 canvas 内容
|
|
607
|
-
const ctx = this.canvas.getContext('2d');
|
|
608
|
-
if (ctx) {
|
|
609
|
-
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
610
|
-
}
|
|
611
|
-
} catch (error) {
|
|
612
|
-
// 忽略清理错误
|
|
613
|
-
}
|
|
614
|
-
this.canvas = null;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
this.project = null;
|
|
618
|
-
this.initialized = false;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|