foliko 1.1.7 → 1.1.9
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/.agent/agents/poster-expert.md +228 -0
- package/.agent/data/default.json +10 -0
- package/.agent/data/plugins-state.json +1 -1
- package/.agent/mcp_config.json +0 -6
- package/.agent/plugins/test-plugin.py +15 -0
- package/.agent/plugins/test_nested_plugin.py +85 -0
- package/.agent/sessions/cli_default.json +83 -803
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
- package/.claude/settings.local.json +8 -1
- package/cli/src/ui/chat-ui.js +32 -21
- package/foliko_poster.png +0 -0
- package/output/business_poster_final.png +0 -0
- package/output/emoji_test_v2.png +0 -0
- package/output/foliko-ai-launch-poster.png +0 -0
- package/output/foliko-poster.png +0 -0
- package/output/muji_style_poster.png +0 -0
- package/output/new_product_launch.png +0 -0
- package/output/news_poster_625yi_subsidy.png +0 -0
- package/output/tech_future_2026.png +0 -0
- package/output/tech_future_poster.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +60 -7
- package/plugins/extension-executor-plugin.js +32 -22
- package/plugins/feishu-plugin.js +2 -2
- package/plugins/python-plugin-loader.js +461 -40
- package/plugins/python-plugin-loader.js.bak +856 -0
- package/plugins/subagent-plugin.js +0 -121
- package/plugins/telegram-plugin.js +2 -2
- package/plugins/weixin-plugin.js +40 -31
- package/poster.png +0 -0
- package/skills/foliko-dev/SKILL.md +6 -0
- package/skills/python-plugin-dev/SKILL.md +124 -2
- package/src/core/agent-chat.js +220 -281
- package/src/core/agent.js +127 -29
- package/src/executors/mcp-executor.js +282 -26
- package/src/utils/plugin-helpers.js +22 -4
- package/system.md +362 -109
- package/test-scan.js +18 -0
- package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +0 -15
- package/.agent/memory/feedback/mnt88ja3-al4fuy.md +0 -9
- package/.agent/plugins/poster-plugin/README.md +0 -304
- package/.agent/plugins/poster-plugin/emojis/rocket.png +0 -1
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/SegoeUI Emoji.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +0 -13
- package/.agent/plugins/poster-plugin/package.json +0 -29
- package/.agent/plugins/poster-plugin/src/canvas.js +0 -224
- package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
- package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
- package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -120
- package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -153
- package/.agent/plugins/poster-plugin/src/components/button.js +0 -124
- package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
- package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/columns.js +0 -107
- package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
- package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
- package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
- package/.agent/plugins/poster-plugin/src/components/grid.js +0 -118
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -144
- package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -155
- package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
- package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
- package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
- package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
- package/.agent/plugins/poster-plugin/src/components/quote.js +0 -97
- package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -193
- package/.agent/plugins/poster-plugin/src/components/seal.js +0 -146
- package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
- package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
- package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
- package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -52
- package/.agent/plugins/poster-plugin/src/composer.js +0 -2146
- package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -60
- package/.agent/plugins/poster-plugin/src/elements/background.js +0 -84
- package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
- package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
- package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -230
- package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
- package/.agent/plugins/poster-plugin/src/elements/text.js +0 -109
- package/.agent/plugins/poster-plugin/src/fonts.js +0 -243
- package/.agent/plugins/poster-plugin/src/index.js +0 -2081
- package/.agent/plugins/poster-plugin/src/presets.js +0 -36
- package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
- package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
- package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
- package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
- package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
- package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
- package/.agent/plugins/poster-plugin/test-background.svg +0 -1
- package/.agent/plugins/poster-plugin/test-full-poster.svg +0 -2
- package/.agent/plugins/poster-plugin/test-image.png +0 -0
- package/.agent/plugins/poster-plugin/yarn.lock +0 -1007
- package/output/zen_silence.png +0 -0
|
@@ -1,2146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 海报组件化生成器
|
|
3
|
-
*
|
|
4
|
-
* 支持 JSON 配置驱动,一次调用生成完整海报
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const paper = require('paper')
|
|
8
|
-
|
|
9
|
-
// 导入新组件
|
|
10
|
-
const createButton = require('./components/button')
|
|
11
|
-
const createIcon = require('./components/icon')
|
|
12
|
-
const createQRCode = require('./components/qrcode')
|
|
13
|
-
const createFrame = require('./components/frame')
|
|
14
|
-
const createBubble = require('./components/bubble')
|
|
15
|
-
const createRibbon = require('./components/ribbon')
|
|
16
|
-
const createSeal = require('./components/seal')
|
|
17
|
-
const createHighlightText = require('./components/highlightText')
|
|
18
|
-
const createBarcode = require('./components/barcode')
|
|
19
|
-
const { loadImageAsRaster } = require('./utils/imageLoader')
|
|
20
|
-
|
|
21
|
-
// 组件包装函数
|
|
22
|
-
async function createButtonComponent(project, canvas, args) {
|
|
23
|
-
return await createButton(project, args)
|
|
24
|
-
}
|
|
25
|
-
async function createIconComponent(project, canvas, args) {
|
|
26
|
-
return await createIcon(project, args)
|
|
27
|
-
}
|
|
28
|
-
async function createQRCodeComponent(project, canvas, args) {
|
|
29
|
-
return await createQRCode(project, args)
|
|
30
|
-
}
|
|
31
|
-
async function createFrameComponent(project, canvas, args) {
|
|
32
|
-
return await createFrame(project, args)
|
|
33
|
-
}
|
|
34
|
-
async function createBubbleComponent(project, canvas, args) {
|
|
35
|
-
return await createBubble(project, args)
|
|
36
|
-
}
|
|
37
|
-
async function createRibbonComponent(project, canvas, args) {
|
|
38
|
-
return await createRibbon(project, args)
|
|
39
|
-
}
|
|
40
|
-
async function createSealComponent(project, canvas, args) {
|
|
41
|
-
return await createSeal(project, args)
|
|
42
|
-
}
|
|
43
|
-
async function createHighlightTextComponent(project, canvas, args) {
|
|
44
|
-
return await createHighlightText(project, args)
|
|
45
|
-
}
|
|
46
|
-
async function createBarcodeComponent(project, canvas, args) {
|
|
47
|
-
return await createBarcode(project, args)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* 辅助函数:将元素添加到活跃层
|
|
52
|
-
*/
|
|
53
|
-
function addToLayer(project, element) {
|
|
54
|
-
if (project && project.activeLayer && element) {
|
|
55
|
-
if (element.insert) {
|
|
56
|
-
project.activeLayer.addChild(element)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 辅助函数:批量添加元素到活跃层
|
|
63
|
-
*/
|
|
64
|
-
function addAllToLayer(project, elements) {
|
|
65
|
-
if (project && project.activeLayer && elements) {
|
|
66
|
-
elements.forEach(el => {
|
|
67
|
-
if (el.id && el.type) {
|
|
68
|
-
const item = project.getItem({ id: el.id })
|
|
69
|
-
if (item) project.activeLayer.addChild(item)
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 组件类型注册表
|
|
77
|
-
*/
|
|
78
|
-
const COMPONENT_TYPES = {
|
|
79
|
-
// 基础元素
|
|
80
|
-
background: 'background',
|
|
81
|
-
rectangle: 'rectangle',
|
|
82
|
-
circle: 'circle',
|
|
83
|
-
line: 'line',
|
|
84
|
-
polygon: 'polygon',
|
|
85
|
-
text: 'text',
|
|
86
|
-
artText: 'artText',
|
|
87
|
-
image: 'image',
|
|
88
|
-
svg: 'svg',
|
|
89
|
-
imageFrame: 'imageFrame',
|
|
90
|
-
// 布局组件
|
|
91
|
-
columns: 'columns',
|
|
92
|
-
grid: 'grid',
|
|
93
|
-
// 装饰组件
|
|
94
|
-
star: 'star',
|
|
95
|
-
arrow: 'arrow',
|
|
96
|
-
progressCircle: 'progressCircle',
|
|
97
|
-
chip: 'chip',
|
|
98
|
-
chart: 'chart',
|
|
99
|
-
watermark: 'watermark',
|
|
100
|
-
table: 'table',
|
|
101
|
-
// 高级组件
|
|
102
|
-
card: 'card',
|
|
103
|
-
badge: 'badge',
|
|
104
|
-
cta: 'cta',
|
|
105
|
-
feature: 'feature',
|
|
106
|
-
featureGrid: 'featureGrid',
|
|
107
|
-
divider: 'divider',
|
|
108
|
-
avatar: 'avatar',
|
|
109
|
-
progress: 'progress',
|
|
110
|
-
rating: 'rating',
|
|
111
|
-
quote: 'quote',
|
|
112
|
-
statCard: 'statCard',
|
|
113
|
-
tagCloud: 'tagCloud',
|
|
114
|
-
stepper: 'stepper',
|
|
115
|
-
timeline: 'timeline',
|
|
116
|
-
listItem: 'listItem',
|
|
117
|
-
notification: 'notification',
|
|
118
|
-
// 设计组件
|
|
119
|
-
button: 'button',
|
|
120
|
-
icon: 'icon',
|
|
121
|
-
qrcode: 'qrcode',
|
|
122
|
-
frame: 'frame',
|
|
123
|
-
bubble: 'bubble',
|
|
124
|
-
ribbon: 'ribbon',
|
|
125
|
-
seal: 'seal',
|
|
126
|
-
highlightText: 'highlightText',
|
|
127
|
-
barcode: 'barcode',
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* 从配置创建海报
|
|
132
|
-
*
|
|
133
|
-
* @param {Object} project - Paper.js 项目
|
|
134
|
-
* @param {Object} canvas - 画布对象
|
|
135
|
-
* @param {Object} config - 海报配置
|
|
136
|
-
* @returns {Object} 创建结果
|
|
137
|
-
*/
|
|
138
|
-
async function createFromConfig(project, canvas, config) {
|
|
139
|
-
const { components = [] } = config
|
|
140
|
-
const results = []
|
|
141
|
-
|
|
142
|
-
for (const component of components) {
|
|
143
|
-
const result = await createComponent(project, canvas, component)
|
|
144
|
-
results.push(result)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 将所有创建的元素添加到活动层
|
|
148
|
-
if (project && project.activeLayer) {
|
|
149
|
-
project.activeLayer.addChildren(project.layers.flatMap(l => l.children))
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
success: true,
|
|
154
|
-
componentCount: results.length,
|
|
155
|
-
results,
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 根据配置创建单个组件
|
|
161
|
-
*/
|
|
162
|
-
async function createComponent(project, canvas, config) {
|
|
163
|
-
const { type, ...args } = config
|
|
164
|
-
|
|
165
|
-
switch (type) {
|
|
166
|
-
// 基础元素
|
|
167
|
-
case 'background':
|
|
168
|
-
return await createBackgroundElement(project, canvas, args)
|
|
169
|
-
case 'rectangle':
|
|
170
|
-
return createRectangleElement(project, args)
|
|
171
|
-
case 'circle':
|
|
172
|
-
return createCircleElement(project, args)
|
|
173
|
-
case 'line':
|
|
174
|
-
return createLineElement(project, args)
|
|
175
|
-
case 'polygon':
|
|
176
|
-
return createPolygonElement(project, args)
|
|
177
|
-
case 'text':
|
|
178
|
-
return createTextElement(project, args)
|
|
179
|
-
case 'artText':
|
|
180
|
-
return await createArtTextElement(project, args)
|
|
181
|
-
case 'image':
|
|
182
|
-
return await createImageElement(project, args)
|
|
183
|
-
case 'svg':
|
|
184
|
-
return await createSVGElement(project, args)
|
|
185
|
-
case 'imageFrame':
|
|
186
|
-
return await createImageFrameComponent(project, canvas, args)
|
|
187
|
-
case 'columns':
|
|
188
|
-
return await createColumnsComponent(project, canvas, args)
|
|
189
|
-
case 'grid':
|
|
190
|
-
return await createGridComponent(project, canvas, args)
|
|
191
|
-
|
|
192
|
-
// 装饰组件
|
|
193
|
-
case 'star':
|
|
194
|
-
return await createStarComponent(project, canvas, args)
|
|
195
|
-
case 'arrow':
|
|
196
|
-
return await createArrowComponent(project, canvas, args)
|
|
197
|
-
case 'progressCircle':
|
|
198
|
-
return await createProgressCircleComponent(project, canvas, args)
|
|
199
|
-
case 'chip':
|
|
200
|
-
return await createChipComponent(project, canvas, args)
|
|
201
|
-
case 'chart':
|
|
202
|
-
return await createChartComponent(project, canvas, args)
|
|
203
|
-
case 'watermark':
|
|
204
|
-
return await createWatermarkComponent(project, canvas, args)
|
|
205
|
-
case 'table':
|
|
206
|
-
return await createTableComponent(project, canvas, args)
|
|
207
|
-
|
|
208
|
-
// 高级组件
|
|
209
|
-
case 'card':
|
|
210
|
-
return await createCardComponent(project, canvas, args)
|
|
211
|
-
case 'badge':
|
|
212
|
-
return await createBadgeComponent(project, canvas, args)
|
|
213
|
-
case 'cta':
|
|
214
|
-
return await createCTAComponent(project, canvas, args)
|
|
215
|
-
case 'feature':
|
|
216
|
-
return await createFeatureComponent(project, canvas, args)
|
|
217
|
-
case 'featureGrid':
|
|
218
|
-
return await createFeatureGridComponent(project, canvas, args)
|
|
219
|
-
case 'divider':
|
|
220
|
-
return await createDividerComponent(project, canvas, args)
|
|
221
|
-
case 'avatar':
|
|
222
|
-
return await createAvatarComponent(project, canvas, args)
|
|
223
|
-
case 'progress':
|
|
224
|
-
return await createProgressComponent(project, canvas, args)
|
|
225
|
-
case 'rating':
|
|
226
|
-
return await createRatingComponent(project, canvas, args)
|
|
227
|
-
case 'quote':
|
|
228
|
-
return await createQuoteComponent(project, canvas, args)
|
|
229
|
-
case 'statCard':
|
|
230
|
-
return await createStatCardComponent(project, canvas, args)
|
|
231
|
-
case 'tagCloud':
|
|
232
|
-
return await createTagCloudComponent(project, canvas, args)
|
|
233
|
-
case 'stepper':
|
|
234
|
-
return await createStepperComponent(project, canvas, args)
|
|
235
|
-
case 'timeline':
|
|
236
|
-
return await createTimelineComponent(project, canvas, args)
|
|
237
|
-
case 'listItem':
|
|
238
|
-
return await createListItemComponent(project, canvas, args)
|
|
239
|
-
case 'notification':
|
|
240
|
-
return await createNotificationComponent(project, canvas, args)
|
|
241
|
-
|
|
242
|
-
// 设计组件
|
|
243
|
-
case 'button':
|
|
244
|
-
return await createButtonComponent(project, canvas, args)
|
|
245
|
-
case 'icon':
|
|
246
|
-
return await createIconComponent(project, canvas, args)
|
|
247
|
-
case 'qrcode':
|
|
248
|
-
return await createQRCodeComponent(project, canvas, args)
|
|
249
|
-
case 'frame':
|
|
250
|
-
return await createFrameComponent(project, canvas, args)
|
|
251
|
-
case 'bubble':
|
|
252
|
-
return await createBubbleComponent(project, canvas, args)
|
|
253
|
-
case 'ribbon':
|
|
254
|
-
return await createRibbonComponent(project, canvas, args)
|
|
255
|
-
case 'seal':
|
|
256
|
-
return await createSealComponent(project, canvas, args)
|
|
257
|
-
case 'highlightText':
|
|
258
|
-
return await createHighlightTextComponent(project, canvas, args)
|
|
259
|
-
case 'barcode':
|
|
260
|
-
return createBarcodeComponent(project, canvas, args)
|
|
261
|
-
|
|
262
|
-
default:
|
|
263
|
-
return { success: false, error: `Unknown component type: ${type}` }
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ============= 基础元素创建函数 =============
|
|
268
|
-
|
|
269
|
-
function createBackgroundElement(project, canvas, { color, gradient, image }) {
|
|
270
|
-
if (image) {
|
|
271
|
-
const fs = require('fs')
|
|
272
|
-
const path = require('path')
|
|
273
|
-
|
|
274
|
-
// 确保 image 是字符串
|
|
275
|
-
if (typeof image !== 'string') {
|
|
276
|
-
return { success: false, error: 'Background image must be a string' }
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// 本地文件路径
|
|
280
|
-
let absolutePath = image
|
|
281
|
-
if (!path.isAbsolute(absolutePath)) {
|
|
282
|
-
absolutePath = path.join(process.cwd(), absolutePath)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (!fs.existsSync(absolutePath)) {
|
|
286
|
-
throw new Error(`背景图片文件不存在: ${absolutePath}`)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const buffer = fs.readFileSync(absolutePath)
|
|
290
|
-
const ext = path.extname(absolutePath).toLowerCase()
|
|
291
|
-
const mimeTypes = {
|
|
292
|
-
'.png': 'image/png',
|
|
293
|
-
'.jpg': 'image/jpeg',
|
|
294
|
-
'.jpeg': 'image/jpeg',
|
|
295
|
-
'.gif': 'image/gif',
|
|
296
|
-
'.webp': 'image/webp',
|
|
297
|
-
'.bmp': 'image/bmp'
|
|
298
|
-
}
|
|
299
|
-
const mimeType = mimeTypes[ext] || 'image/png'
|
|
300
|
-
const imageUrl = `data:${mimeType};base64,${buffer.toString('base64')}`
|
|
301
|
-
|
|
302
|
-
const raster = new paper.Raster(imageUrl)
|
|
303
|
-
|
|
304
|
-
// 添加到项目活动层
|
|
305
|
-
if (project && project.activeLayer) {
|
|
306
|
-
project.activeLayer.addChild(raster)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
raster.onLoad = () => {
|
|
310
|
-
// 计算缩放比例,使图片覆盖整个画布(cover 模式)
|
|
311
|
-
const canvasRatio = canvas.width / canvas.height
|
|
312
|
-
const imageRatio = raster.width / raster.height
|
|
313
|
-
|
|
314
|
-
let scaledWidth, scaledHeight, offsetX, offsetY
|
|
315
|
-
|
|
316
|
-
if (imageRatio > canvasRatio) {
|
|
317
|
-
// 图片更宽,以高度为基准缩放
|
|
318
|
-
scaledHeight = canvas.height
|
|
319
|
-
scaledWidth = raster.width * (canvas.height / raster.height)
|
|
320
|
-
offsetX = (canvas.width - scaledWidth) / 2
|
|
321
|
-
offsetY = 0
|
|
322
|
-
} else {
|
|
323
|
-
// 图片更高,以宽度为基准缩放
|
|
324
|
-
scaledWidth = canvas.width
|
|
325
|
-
scaledHeight = raster.height * (canvas.width / raster.width)
|
|
326
|
-
offsetX = 0
|
|
327
|
-
offsetY = (canvas.height - scaledHeight) / 2
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
raster.bounds = new paper.Rectangle(offsetX, offsetY, scaledWidth, scaledHeight)
|
|
331
|
-
raster.sendToBack()
|
|
332
|
-
}
|
|
333
|
-
} else if (gradient) {
|
|
334
|
-
const paperColors = gradient.colors.map(c => new paper.Color(c))
|
|
335
|
-
const { type, direction } = gradient
|
|
336
|
-
|
|
337
|
-
if (type === 'linear') {
|
|
338
|
-
const angle = (direction || 45) * Math.PI / 180
|
|
339
|
-
const diagonal = Math.sqrt(canvas.width ** 2 + canvas.height ** 2)
|
|
340
|
-
const centerX = canvas.width / 2
|
|
341
|
-
const centerY = canvas.height / 2
|
|
342
|
-
const start = new paper.Point(
|
|
343
|
-
centerX - Math.cos(angle) * diagonal / 2,
|
|
344
|
-
centerY - Math.sin(angle) * diagonal / 2
|
|
345
|
-
)
|
|
346
|
-
const stop = new paper.Point(
|
|
347
|
-
centerX + Math.cos(angle) * diagonal / 2,
|
|
348
|
-
centerY + Math.sin(angle) * diagonal / 2
|
|
349
|
-
)
|
|
350
|
-
project.activeLayer.fillColor = new paper.Color({
|
|
351
|
-
gradient: { stops: paperColors },
|
|
352
|
-
origin: start,
|
|
353
|
-
destination: stop,
|
|
354
|
-
})
|
|
355
|
-
} else {
|
|
356
|
-
const center = new paper.Point(canvas.width / 2, canvas.height / 2)
|
|
357
|
-
const radius = Math.max(canvas.width, canvas.height) / 2
|
|
358
|
-
project.activeLayer.fillColor = new paper.Color({
|
|
359
|
-
gradient: { stops: paperColors },
|
|
360
|
-
origin: center,
|
|
361
|
-
destination: center.add(new paper.Point(radius, 0)),
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
} else if (color) {
|
|
365
|
-
project.activeLayer.fillColor = new paper.Color(color)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return { success: true, type: 'background' }
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function createRectangleElement(project, { x, y, width, height, fill, stroke, strokeWidth, radius, opacity }) {
|
|
372
|
-
const rect = new paper.Path.Rectangle({
|
|
373
|
-
point: [x, y],
|
|
374
|
-
size: [width, height],
|
|
375
|
-
radius: radius || 0,
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
if (fill) rect.fillColor = new paper.Color(fill)
|
|
379
|
-
if (stroke) {
|
|
380
|
-
rect.strokeColor = new paper.Color(stroke)
|
|
381
|
-
rect.strokeWidth = strokeWidth || 1
|
|
382
|
-
}
|
|
383
|
-
if (opacity !== undefined) rect.opacity = opacity
|
|
384
|
-
|
|
385
|
-
if (project && project.activeLayer) {
|
|
386
|
-
project.activeLayer.addChild(rect)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return { success: true, id: rect.id, type: 'rectangle' }
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function createCircleElement(project, { x, y, cx, cy, radius, rx, ry, fill, stroke, strokeWidth, opacity }) {
|
|
393
|
-
// 兼容 x, y, radius 或 cx, cy, rx, ry 格式
|
|
394
|
-
const centerX = cx || x
|
|
395
|
-
const centerY = cy || y
|
|
396
|
-
const radiusX = rx || radius || 30
|
|
397
|
-
const radiusY = ry || radius || 30
|
|
398
|
-
|
|
399
|
-
const circle = new paper.Path.Ellipse({
|
|
400
|
-
center: [centerX, centerY],
|
|
401
|
-
radius: [radiusX, radiusY],
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
if (fill) circle.fillColor = new paper.Color(fill)
|
|
405
|
-
if (stroke) {
|
|
406
|
-
circle.strokeColor = new paper.Color(stroke)
|
|
407
|
-
circle.strokeWidth = strokeWidth || 1
|
|
408
|
-
}
|
|
409
|
-
if (opacity !== undefined) circle.opacity = opacity
|
|
410
|
-
|
|
411
|
-
if (project && project.activeLayer) {
|
|
412
|
-
project.activeLayer.addChild(circle)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return { success: true, id: circle.id, type: 'circle' }
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function createLineElement(project, { x1, y1, x2, y2, stroke, strokeWidth }) {
|
|
419
|
-
const line = new paper.Path.Line({
|
|
420
|
-
from: [x1, y1],
|
|
421
|
-
to: [x2, y2],
|
|
422
|
-
strokeColor: new paper.Color(stroke || '#ffffff'),
|
|
423
|
-
strokeWidth: strokeWidth || 2,
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
if (project && project.activeLayer) {
|
|
427
|
-
project.activeLayer.addChild(line)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return { success: true, id: line.id, type: 'line' }
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function createPolygonElement(project, { cx, cy, radius, sides, fill, stroke, strokeWidth, opacity }) {
|
|
434
|
-
const polygon = new paper.Path.RegularPolygon({
|
|
435
|
-
center: [cx, cy],
|
|
436
|
-
radius: radius,
|
|
437
|
-
sides: sides,
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
if (fill) polygon.fillColor = new paper.Color(fill)
|
|
441
|
-
if (stroke) {
|
|
442
|
-
polygon.strokeColor = new paper.Color(stroke)
|
|
443
|
-
polygon.strokeWidth = strokeWidth || 1
|
|
444
|
-
}
|
|
445
|
-
if (opacity !== undefined) polygon.opacity = opacity
|
|
446
|
-
|
|
447
|
-
if (project && project.activeLayer) {
|
|
448
|
-
project.activeLayer.addChild(polygon)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return { success: true, id: polygon.id, type: 'polygon' }
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function createTextElement(project, { text, x, y, fontSize, fontFamily, color, align, shadow }) {
|
|
455
|
-
const { validateFont, getDefaultFont } = require('./fonts')
|
|
456
|
-
|
|
457
|
-
const textItem = new paper.PointText({
|
|
458
|
-
point: [x, y],
|
|
459
|
-
content: text,
|
|
460
|
-
fontSize: fontSize || 48,
|
|
461
|
-
fontFamily: validateFont(fontFamily) || getDefaultFont(),
|
|
462
|
-
fillColor: new paper.Color(color || '#ffffff'),
|
|
463
|
-
justification: align || 'left',
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
if (shadow) {
|
|
467
|
-
textItem.shadowColor = new paper.Color(shadow.color)
|
|
468
|
-
textItem.shadowBlur = shadow.blur || 5
|
|
469
|
-
textItem.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (project && project.activeLayer) {
|
|
473
|
-
project.activeLayer.addChild(textItem)
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return { success: true, id: textItem.id, type: 'text' }
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function createArtTextElement(project, { text, x, y, fontSize, fontFamily, gradient, strokeColor, strokeWidth, shadow }) {
|
|
480
|
-
const { validateFont, getDefaultFont } = require('./fonts')
|
|
481
|
-
|
|
482
|
-
const textItem = new paper.PointText({
|
|
483
|
-
point: [x, y],
|
|
484
|
-
content: text,
|
|
485
|
-
fontSize: fontSize || 120,
|
|
486
|
-
fontFamily: validateFont(fontFamily) || getDefaultFont(),
|
|
487
|
-
fillColor: gradient ? new paper.Color(gradient.colors[0]) : new paper.Color('#ffffff'),
|
|
488
|
-
justification: 'center',
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
if (gradient && gradient.colors.length > 0) {
|
|
492
|
-
const colors = gradient.colors.map(c => new paper.Color(c))
|
|
493
|
-
textItem.fillColor = new paper.Color({
|
|
494
|
-
gradient: { stops: colors },
|
|
495
|
-
origin: textItem.bounds.topLeft,
|
|
496
|
-
destination: textItem.bounds.topRight,
|
|
497
|
-
})
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (strokeColor) {
|
|
501
|
-
textItem.strokeColor = new paper.Color(strokeColor)
|
|
502
|
-
textItem.strokeWidth = strokeWidth || 2
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (shadow) {
|
|
506
|
-
textItem.shadowColor = new paper.Color(shadow.color)
|
|
507
|
-
textItem.shadowBlur = shadow.blur || 10
|
|
508
|
-
textItem.shadowOffset = new paper.Point(shadow.offsetX || 3, shadow.offsetY || 3)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (project && project.activeLayer) {
|
|
512
|
-
project.activeLayer.addChild(textItem)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return { success: true, id: textItem.id, type: 'artText' }
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async function createImageElement(project, { src, x = 0, y = 0, width, height, opacity = 1 }) {
|
|
519
|
-
try {
|
|
520
|
-
const { raster } = await loadImageAsRaster(project, src, { x, y, width, height }, opacity)
|
|
521
|
-
|
|
522
|
-
return {
|
|
523
|
-
success: true,
|
|
524
|
-
id: raster.id,
|
|
525
|
-
type: 'image',
|
|
526
|
-
}
|
|
527
|
-
} catch (err) {
|
|
528
|
-
return { success: false, error: `Failed to load image: ${err.message}` }
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const fs = require('fs')
|
|
533
|
-
|
|
534
|
-
async function createSVGElement(project, { src, x = 0, y = 0, width, height, opacity = 1 }) {
|
|
535
|
-
const fs = require('fs')
|
|
536
|
-
const path = require('path')
|
|
537
|
-
|
|
538
|
-
// 确保 src 是字符串
|
|
539
|
-
if (typeof src !== 'string') {
|
|
540
|
-
return { success: false, error: 'SVG source must be a string' }
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
let svgContent = src
|
|
544
|
-
|
|
545
|
-
// 如果是文件路径,读取文件内容
|
|
546
|
-
if (!src.startsWith('<') && !src.startsWith('<?xml')) {
|
|
547
|
-
try {
|
|
548
|
-
let filePath = src
|
|
549
|
-
if (!path.isAbsolute(filePath)) {
|
|
550
|
-
filePath = path.join(process.cwd(), filePath)
|
|
551
|
-
}
|
|
552
|
-
svgContent = fs.readFileSync(filePath, 'utf8')
|
|
553
|
-
} catch (e) {
|
|
554
|
-
return { success: false, error: `Failed to read SVG file: ${e.message}` }
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// 导入 SVG 到指定项目
|
|
559
|
-
let svg
|
|
560
|
-
try {
|
|
561
|
-
svg = project.importSVG(svgContent)
|
|
562
|
-
} catch (e) {
|
|
563
|
-
return { success: false, error: `Failed to import SVG: ${e.message}` }
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (!svg) {
|
|
567
|
-
return { success: false, error: 'Failed to import SVG' }
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// 确保 SVG 添加到活动层
|
|
571
|
-
if (project && project.activeLayer && svg.parent !== project.activeLayer) {
|
|
572
|
-
project.activeLayer.addChild(svg)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// 设置位置
|
|
576
|
-
svg.position = new paper.Point(x, y)
|
|
577
|
-
|
|
578
|
-
// 设置尺寸
|
|
579
|
-
if (width && height) {
|
|
580
|
-
const scaleX = width / svg.bounds.width
|
|
581
|
-
const scaleY = height / svg.bounds.height
|
|
582
|
-
svg.scale(Math.min(scaleX, scaleY), svg.bounds.center)
|
|
583
|
-
} else if (width) {
|
|
584
|
-
svg.scale(width / svg.bounds.width, svg.bounds.center)
|
|
585
|
-
} else if (height) {
|
|
586
|
-
svg.scale(height / svg.bounds.height, svg.bounds.center)
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// 设置透明度
|
|
590
|
-
if (opacity !== undefined) {
|
|
591
|
-
svg.opacity = opacity
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return {
|
|
595
|
-
success: true,
|
|
596
|
-
id: svg.id,
|
|
597
|
-
type: 'svg',
|
|
598
|
-
width: svg.bounds.width,
|
|
599
|
-
height: svg.bounds.height,
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// ============= 高级组件创建函数 =============
|
|
604
|
-
|
|
605
|
-
function createCardComponent(project, canvas, {
|
|
606
|
-
x, y, width, height,
|
|
607
|
-
background, border, borderWidth, radius,
|
|
608
|
-
title, titleSize, titleColor,
|
|
609
|
-
subtitle, subtitleSize, subtitleColor,
|
|
610
|
-
padding = 20,
|
|
611
|
-
}) {
|
|
612
|
-
const elements = []
|
|
613
|
-
|
|
614
|
-
// 卡片背景
|
|
615
|
-
const card = new paper.Path.Rectangle({
|
|
616
|
-
point: [x, y],
|
|
617
|
-
size: [width, height],
|
|
618
|
-
radius: radius || 0,
|
|
619
|
-
})
|
|
620
|
-
card.fillColor = new paper.Color(background || '#ffffff')
|
|
621
|
-
if (border) {
|
|
622
|
-
card.strokeColor = new paper.Color(border)
|
|
623
|
-
card.strokeWidth = borderWidth || 1
|
|
624
|
-
}
|
|
625
|
-
elements.push({ type: 'rectangle', id: card.id })
|
|
626
|
-
|
|
627
|
-
// 标题
|
|
628
|
-
if (title) {
|
|
629
|
-
const titleText = new paper.PointText({
|
|
630
|
-
point: [x + padding, y + padding + (titleSize || 24)],
|
|
631
|
-
content: title,
|
|
632
|
-
fontSize: titleSize || 24,
|
|
633
|
-
fillColor: new paper.Color(titleColor || '#000000'),
|
|
634
|
-
justification: 'left',
|
|
635
|
-
})
|
|
636
|
-
elements.push({ type: 'text', id: titleText.id })
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// 副标题
|
|
640
|
-
if (subtitle) {
|
|
641
|
-
const subY = title ? y + padding + (titleSize || 24) + (subtitleSize || 16) + 10 : y + padding
|
|
642
|
-
const subtitleText = new paper.PointText({
|
|
643
|
-
point: [x + padding, subY],
|
|
644
|
-
content: subtitle,
|
|
645
|
-
fontSize: subtitleSize || 16,
|
|
646
|
-
fillColor: new paper.Color(subtitleColor || '#666666'),
|
|
647
|
-
justification: 'left',
|
|
648
|
-
})
|
|
649
|
-
elements.push({ type: 'text', id: subtitleText.id })
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
return { success: true, elements, type: 'card' }
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function createBadgeComponent(project, canvas, {
|
|
656
|
-
x, y, text,
|
|
657
|
-
background = '#007bff', color = '#ffffff',
|
|
658
|
-
border, fontSize = 18, padding = 15, radius = 4,
|
|
659
|
-
}) {
|
|
660
|
-
const textWidth = text.length * fontSize * 0.6
|
|
661
|
-
const badgeWidth = textWidth + padding * 2
|
|
662
|
-
const badgeHeight = fontSize + padding * 2
|
|
663
|
-
const badgeX = x - badgeWidth / 2
|
|
664
|
-
|
|
665
|
-
const badge = new paper.Path.Rectangle({
|
|
666
|
-
point: [badgeX, y],
|
|
667
|
-
size: [badgeWidth, badgeHeight],
|
|
668
|
-
radius: radius,
|
|
669
|
-
})
|
|
670
|
-
badge.fillColor = new paper.Color(background)
|
|
671
|
-
if (border) badge.strokeColor = new paper.Color(border)
|
|
672
|
-
|
|
673
|
-
const badgeText = new paper.PointText({
|
|
674
|
-
point: [x, y + badgeHeight / 2 + fontSize / 3],
|
|
675
|
-
content: text,
|
|
676
|
-
fontSize: fontSize,
|
|
677
|
-
fillColor: new paper.Color(color),
|
|
678
|
-
justification: 'center',
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
return { success: true, elements: [{ type: 'rectangle', id: badge.id }, { type: 'text', id: badgeText.id }], type: 'badge' }
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
function createCTAComponent(project, canvas, {
|
|
685
|
-
x, y, text,
|
|
686
|
-
background = '#007bff', color = '#ffffff',
|
|
687
|
-
border, fontSize = 20, padding = 25, radius = 8, shadow,
|
|
688
|
-
width: customWidth,
|
|
689
|
-
}) {
|
|
690
|
-
// 确保 text 是字符串
|
|
691
|
-
const textStr = String(text || '')
|
|
692
|
-
// 使用更准确的字符宽度估算:中文约1.0,英文约0.5
|
|
693
|
-
const chineseChars = (textStr.match(/[\u4e00-\u9fa5]/g) || []).length
|
|
694
|
-
const otherChars = textStr.length - chineseChars
|
|
695
|
-
const textWidth = chineseChars * fontSize * 1.0 + otherChars * fontSize * 0.5
|
|
696
|
-
const btnWidth = customWidth || (textWidth + padding * 2)
|
|
697
|
-
const btnHeight = fontSize + padding * 2
|
|
698
|
-
const btnX = x - btnWidth / 2
|
|
699
|
-
|
|
700
|
-
const button = new paper.Path.Rectangle({
|
|
701
|
-
point: [btnX, y],
|
|
702
|
-
size: [btnWidth, btnHeight],
|
|
703
|
-
radius: radius,
|
|
704
|
-
})
|
|
705
|
-
button.fillColor = new paper.Color(background)
|
|
706
|
-
if (border) button.strokeColor = new paper.Color(border)
|
|
707
|
-
|
|
708
|
-
if (shadow) {
|
|
709
|
-
button.shadowColor = new paper.Color(shadow.color || 'rgba(0,0,0,0.3)')
|
|
710
|
-
button.shadowBlur = shadow.blur || 10
|
|
711
|
-
button.shadowOffset = new paper.Point(shadow.offsetX || 0, shadow.offsetY || 4)
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const buttonText = new paper.PointText({
|
|
715
|
-
point: [x, y + btnHeight / 2 + fontSize / 3],
|
|
716
|
-
content: textStr,
|
|
717
|
-
fontSize: fontSize,
|
|
718
|
-
fillColor: new paper.Color(color),
|
|
719
|
-
justification: 'center',
|
|
720
|
-
})
|
|
721
|
-
|
|
722
|
-
// 添加到项目
|
|
723
|
-
if (project && project.activeLayer) {
|
|
724
|
-
project.activeLayer.addChild(button)
|
|
725
|
-
project.activeLayer.addChild(buttonText)
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
return { success: true, elements: [{ type: 'rectangle', id: button.id }, { type: 'text', id: buttonText.id }], type: 'cta' }
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
function createFeatureComponent(project, canvas, {
|
|
732
|
-
x, y, width,
|
|
733
|
-
icon, title, description,
|
|
734
|
-
iconColor = '#007bff', titleColor = '#ffffff', descColor = '#aaaaaa',
|
|
735
|
-
iconSize = 32, titleSize = 20, descSize = 14,
|
|
736
|
-
}) {
|
|
737
|
-
const elements = []
|
|
738
|
-
const padding = 15
|
|
739
|
-
let currentY = y
|
|
740
|
-
|
|
741
|
-
if (icon) {
|
|
742
|
-
elements.push(new paper.PointText({
|
|
743
|
-
point: [x + padding, currentY + iconSize],
|
|
744
|
-
content: icon,
|
|
745
|
-
fontSize: iconSize,
|
|
746
|
-
fillColor: new paper.Color(iconColor),
|
|
747
|
-
justification: 'left',
|
|
748
|
-
}))
|
|
749
|
-
currentY += iconSize + 5
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (title) {
|
|
753
|
-
elements.push(new paper.PointText({
|
|
754
|
-
point: [x + padding, currentY + titleSize],
|
|
755
|
-
content: title,
|
|
756
|
-
fontSize: titleSize,
|
|
757
|
-
fillColor: new paper.Color(titleColor),
|
|
758
|
-
justification: 'left',
|
|
759
|
-
}))
|
|
760
|
-
currentY += titleSize + 5
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (description) {
|
|
764
|
-
elements.push(new paper.PointText({
|
|
765
|
-
point: [x + padding, currentY + descSize],
|
|
766
|
-
content: description,
|
|
767
|
-
fontSize: descSize,
|
|
768
|
-
fillColor: new paper.Color(descColor),
|
|
769
|
-
justification: 'left',
|
|
770
|
-
}))
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return { success: true, elements, type: 'feature' }
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function createFeatureGridComponent(project, canvas, {
|
|
777
|
-
x, y,
|
|
778
|
-
columns = 3, itemWidth = 200, itemHeight = 120, gap = 20,
|
|
779
|
-
items = [],
|
|
780
|
-
background = '#1a1a2e', borderColor = '#00d9ff', radius = 8,
|
|
781
|
-
}) {
|
|
782
|
-
const elements = []
|
|
783
|
-
|
|
784
|
-
// 确保 items 是数组
|
|
785
|
-
if (!Array.isArray(items)) {
|
|
786
|
-
items = []
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const rows = items.length > 0 ? Math.ceil(items.length / columns) : 0
|
|
790
|
-
|
|
791
|
-
for (let i = 0; i < items.length; i++) {
|
|
792
|
-
const item = items[i]
|
|
793
|
-
const col = i % columns
|
|
794
|
-
const row = Math.floor(i / columns)
|
|
795
|
-
const itemX = x + col * (itemWidth + gap)
|
|
796
|
-
const itemY = y + row * (itemHeight + gap)
|
|
797
|
-
|
|
798
|
-
const bg = new paper.Path.Rectangle({
|
|
799
|
-
point: [itemX, itemY],
|
|
800
|
-
size: [itemWidth, itemHeight],
|
|
801
|
-
radius: radius,
|
|
802
|
-
})
|
|
803
|
-
bg.fillColor = new paper.Color(background)
|
|
804
|
-
bg.strokeColor = new paper.Color(borderColor)
|
|
805
|
-
bg.strokeWidth = 0.5
|
|
806
|
-
bg.opacity = 0.8
|
|
807
|
-
elements.push(bg)
|
|
808
|
-
|
|
809
|
-
// 添加到项目
|
|
810
|
-
if (project && project.activeLayer) {
|
|
811
|
-
project.activeLayer.addChild(bg)
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
const padding = 15
|
|
815
|
-
let offsetY = itemY + padding
|
|
816
|
-
|
|
817
|
-
if (item.icon) {
|
|
818
|
-
const iconText = new paper.PointText({
|
|
819
|
-
point: [itemX + padding, offsetY + 24],
|
|
820
|
-
content: item.icon,
|
|
821
|
-
fontSize: 28,
|
|
822
|
-
fillColor: new paper.Color(item.iconColor || '#00ff88'),
|
|
823
|
-
justification: 'left',
|
|
824
|
-
})
|
|
825
|
-
elements.push(iconText)
|
|
826
|
-
if (project && project.activeLayer) {
|
|
827
|
-
project.activeLayer.addChild(iconText)
|
|
828
|
-
}
|
|
829
|
-
offsetY += 35
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
if (item.title) {
|
|
833
|
-
const titleText = new paper.PointText({
|
|
834
|
-
point: [itemX + padding, offsetY + 18],
|
|
835
|
-
content: item.title,
|
|
836
|
-
fontSize: 16,
|
|
837
|
-
fillColor: new paper.Color(item.titleColor || '#ffffff'),
|
|
838
|
-
justification: 'left',
|
|
839
|
-
})
|
|
840
|
-
elements.push(titleText)
|
|
841
|
-
if (project && project.activeLayer) {
|
|
842
|
-
project.activeLayer.addChild(titleText)
|
|
843
|
-
}
|
|
844
|
-
offsetY += 22
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
if (item.description) {
|
|
848
|
-
const descText = new paper.PointText({
|
|
849
|
-
point: [itemX + padding, offsetY + 14],
|
|
850
|
-
content: item.description,
|
|
851
|
-
fontSize: 12,
|
|
852
|
-
fillColor: new paper.Color(item.descColor || '#888888'),
|
|
853
|
-
justification: 'left',
|
|
854
|
-
})
|
|
855
|
-
elements.push(descText)
|
|
856
|
-
if (project && project.activeLayer) {
|
|
857
|
-
project.activeLayer.addChild(descText)
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
return {
|
|
863
|
-
success: true,
|
|
864
|
-
elements,
|
|
865
|
-
type: 'featureGrid',
|
|
866
|
-
width: columns * itemWidth + (columns - 1) * gap,
|
|
867
|
-
height: rows * itemHeight + Math.max(0, rows - 1) * gap,
|
|
868
|
-
rows,
|
|
869
|
-
cols: columns,
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function createDividerComponent(project, canvas, {
|
|
874
|
-
x, y, width, color = '#00d9ff', thickness = 1, style = 'solid', align = 'center',
|
|
875
|
-
}) {
|
|
876
|
-
let startX = x
|
|
877
|
-
let endX = x + width
|
|
878
|
-
|
|
879
|
-
if (align === 'center') {
|
|
880
|
-
startX = x - width / 2
|
|
881
|
-
endX = x + width / 2
|
|
882
|
-
} else if (align === 'right') {
|
|
883
|
-
startX = x - width
|
|
884
|
-
endX = x
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const line = new paper.Path.Line({
|
|
888
|
-
from: [startX, y],
|
|
889
|
-
to: [endX, y],
|
|
890
|
-
strokeColor: new paper.Color(color),
|
|
891
|
-
strokeWidth: thickness,
|
|
892
|
-
})
|
|
893
|
-
|
|
894
|
-
if (style === 'dashed') line.dashArray = [10, 5]
|
|
895
|
-
|
|
896
|
-
return { success: true, id: line.id, type: 'divider' }
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
module.exports = {
|
|
900
|
-
createFromConfig,
|
|
901
|
-
createComponent,
|
|
902
|
-
COMPONENT_TYPES,
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
// ============= 新增组件创建函数 =============
|
|
907
|
-
|
|
908
|
-
function createAvatarComponent(project, canvas, { x, y, size = 80, initials, background = '#6366f1', border, borderWidth = 0, color = '#ffffff' }) {
|
|
909
|
-
const elements = []
|
|
910
|
-
const radius = size / 2
|
|
911
|
-
|
|
912
|
-
const circle = new paper.Path.Circle({
|
|
913
|
-
center: [x, y],
|
|
914
|
-
radius: radius,
|
|
915
|
-
})
|
|
916
|
-
circle.fillColor = new paper.Color(background)
|
|
917
|
-
if (border) {
|
|
918
|
-
circle.strokeColor = new paper.Color(border)
|
|
919
|
-
circle.strokeWidth = borderWidth
|
|
920
|
-
}
|
|
921
|
-
elements.push({ type: 'circle', id: circle.id })
|
|
922
|
-
|
|
923
|
-
if (initials) {
|
|
924
|
-
const text = new paper.PointText({
|
|
925
|
-
point: [x, y + size / 6],
|
|
926
|
-
content: initials.charAt(0).toUpperCase(),
|
|
927
|
-
fontSize: size * 0.4,
|
|
928
|
-
fillColor: new paper.Color(color),
|
|
929
|
-
justification: 'center',
|
|
930
|
-
})
|
|
931
|
-
elements.push({ type: 'text', id: text.id })
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
return { success: true, elements, type: 'avatar', size }
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function createProgressComponent(project, canvas, { x, y, width = 300, height = 20, value = 50, trackColor = '#e0e0e0', fillColor = '#6366f1', radius = 10, showLabel = false, label }) {
|
|
938
|
-
const elements = []
|
|
939
|
-
|
|
940
|
-
const track = new paper.Path.Rectangle({
|
|
941
|
-
point: [x, y],
|
|
942
|
-
size: [width, height],
|
|
943
|
-
radius: radius,
|
|
944
|
-
})
|
|
945
|
-
track.fillColor = new paper.Color(trackColor)
|
|
946
|
-
elements.push({ type: 'rectangle', id: track.id })
|
|
947
|
-
|
|
948
|
-
const progressWidth = (value / 100) * width
|
|
949
|
-
if (progressWidth > 0) {
|
|
950
|
-
const fill = new paper.Path.Rectangle({
|
|
951
|
-
point: [x, y],
|
|
952
|
-
size: [progressWidth, height],
|
|
953
|
-
radius: radius,
|
|
954
|
-
})
|
|
955
|
-
fill.fillColor = new paper.Color(fillColor)
|
|
956
|
-
elements.push({ type: 'rectangle', id: fill.id })
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (showLabel && label) {
|
|
960
|
-
const labelText = new paper.PointText({
|
|
961
|
-
point: [x + width / 2, y - 8],
|
|
962
|
-
content: label,
|
|
963
|
-
fontSize: 14,
|
|
964
|
-
fillColor: new paper.Color('#666666'),
|
|
965
|
-
justification: 'center',
|
|
966
|
-
})
|
|
967
|
-
elements.push({ type: 'text', id: labelText.id })
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
return { success: true, elements, value }
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
function createRatingComponent(project, canvas, { x, y, value = 4, max = 5, size = 24, filledColor = '#fbbf24', emptyColor = '#e5e7eb', gap = 4 }) {
|
|
974
|
-
const elements = []
|
|
975
|
-
|
|
976
|
-
for (let i = 0; i < max; i++) {
|
|
977
|
-
const starX = x + i * (size + gap)
|
|
978
|
-
const filled = i < Math.floor(value)
|
|
979
|
-
|
|
980
|
-
const star = new paper.Path.Star({
|
|
981
|
-
center: [starX + size / 2, y + size / 2],
|
|
982
|
-
points: 5,
|
|
983
|
-
radius1: size / 4,
|
|
984
|
-
radius2: size / 2,
|
|
985
|
-
})
|
|
986
|
-
star.fillColor = new paper.Color(filled ? filledColor : emptyColor)
|
|
987
|
-
elements.push({ type: 'polygon', id: star.id })
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
return { success: true, elements, value }
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
function createQuoteComponent(project, canvas, { x, y, width = 400, text, author, background = '#f8fafc', borderColor = '#6366f1', borderWidth = 4, padding = 20, radius = 8, textColor = '#1e293b', authorColor = '#64748b', fontSize = 18 }) {
|
|
994
|
-
const elements = []
|
|
995
|
-
const lineHeight = 22
|
|
996
|
-
|
|
997
|
-
const bg = new paper.Path.Rectangle({
|
|
998
|
-
point: [x, y],
|
|
999
|
-
size: [width, author ? 80 + fontSize * 2 : 40 + fontSize * 1.5],
|
|
1000
|
-
radius: radius,
|
|
1001
|
-
})
|
|
1002
|
-
bg.fillColor = new paper.Color(background)
|
|
1003
|
-
elements.push({ type: 'rectangle', id: bg.id })
|
|
1004
|
-
|
|
1005
|
-
const border = new paper.Path.Rectangle({
|
|
1006
|
-
point: [x, y],
|
|
1007
|
-
size: [borderWidth, author ? 80 + fontSize * 2 : 40 + fontSize * 1.5],
|
|
1008
|
-
})
|
|
1009
|
-
border.fillColor = new paper.Color(borderColor)
|
|
1010
|
-
elements.push({ type: 'rectangle', id: border.id })
|
|
1011
|
-
|
|
1012
|
-
const quoteMark = new paper.PointText({
|
|
1013
|
-
point: [x + padding + 10, y + padding + fontSize],
|
|
1014
|
-
content: '"',
|
|
1015
|
-
fontSize: fontSize * 2,
|
|
1016
|
-
fillColor: new paper.Color(borderColor),
|
|
1017
|
-
justification: 'left',
|
|
1018
|
-
})
|
|
1019
|
-
elements.push({ type: 'text', id: quoteMark.id })
|
|
1020
|
-
|
|
1021
|
-
const quoteText = new paper.PointText({
|
|
1022
|
-
point: [x + padding + 30, y + padding + fontSize * 1.5],
|
|
1023
|
-
content: text,
|
|
1024
|
-
fontSize: fontSize,
|
|
1025
|
-
fillColor: new paper.Color(textColor),
|
|
1026
|
-
justification: 'left',
|
|
1027
|
-
})
|
|
1028
|
-
elements.push({ type: 'text', id: quoteText.id })
|
|
1029
|
-
|
|
1030
|
-
if (author) {
|
|
1031
|
-
const authorText = new paper.PointText({
|
|
1032
|
-
point: [x + padding, y + padding + fontSize * 2.5 + 10],
|
|
1033
|
-
content: `— ${author}`,
|
|
1034
|
-
fontSize: fontSize * 0.8,
|
|
1035
|
-
fillColor: new paper.Color(authorColor),
|
|
1036
|
-
justification: 'left',
|
|
1037
|
-
})
|
|
1038
|
-
elements.push({ type: 'text', id: authorText.id })
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
return { success: true, elements, type: 'quote' }
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
function createStatCardComponent(project, canvas, { x, y, width = 200, height = 120, label = 'Total', value = '0', change, positive = true, icon, iconColor = '#6366f1', background = '#ffffff', border = '#e5e7eb', radius = 12 }) {
|
|
1045
|
-
const elements = []
|
|
1046
|
-
|
|
1047
|
-
const bg = new paper.Path.Rectangle({
|
|
1048
|
-
point: [x, y],
|
|
1049
|
-
size: [width, height],
|
|
1050
|
-
radius: radius,
|
|
1051
|
-
})
|
|
1052
|
-
bg.fillColor = new paper.Color(background)
|
|
1053
|
-
bg.strokeColor = new paper.Color(border)
|
|
1054
|
-
bg.strokeWidth = 1
|
|
1055
|
-
elements.push({ type: 'rectangle', id: bg.id })
|
|
1056
|
-
|
|
1057
|
-
if (icon) {
|
|
1058
|
-
elements.push(new paper.PointText({
|
|
1059
|
-
point: [x + 20, y + 35],
|
|
1060
|
-
content: icon,
|
|
1061
|
-
fontSize: 24,
|
|
1062
|
-
fillColor: new paper.Color(iconColor),
|
|
1063
|
-
justification: 'left',
|
|
1064
|
-
}))
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
elements.push(new paper.PointText({
|
|
1068
|
-
point: [x + 20, y + 50 + (icon ? 10 : 0)],
|
|
1069
|
-
content: label,
|
|
1070
|
-
fontSize: 14,
|
|
1071
|
-
fillColor: new paper.Color('#64748b'),
|
|
1072
|
-
justification: 'left',
|
|
1073
|
-
}))
|
|
1074
|
-
|
|
1075
|
-
elements.push(new paper.PointText({
|
|
1076
|
-
point: [x + 20, y + 75 + (icon ? 10 : 0)],
|
|
1077
|
-
content: value,
|
|
1078
|
-
fontSize: 28,
|
|
1079
|
-
fillColor: new paper.Color('#1e293b'),
|
|
1080
|
-
justification: 'left',
|
|
1081
|
-
}))
|
|
1082
|
-
|
|
1083
|
-
if (change) {
|
|
1084
|
-
const changeColor = positive ? '#22c55e' : '#ef4444'
|
|
1085
|
-
const changeIcon = positive ? '↑' : '↓'
|
|
1086
|
-
elements.push(new paper.PointText({
|
|
1087
|
-
point: [x + 20, y + 95 + (icon ? 10 : 0)],
|
|
1088
|
-
content: `${changeIcon} ${change}`,
|
|
1089
|
-
fontSize: 14,
|
|
1090
|
-
fillColor: new paper.Color(changeColor),
|
|
1091
|
-
justification: 'left',
|
|
1092
|
-
}))
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
return { success: true, elements, type: 'statCard' }
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function createTagCloudComponent(project, canvas, { x, y, tags = [], fontSize = 14, padding = 12, gap = 10, maxWidth = 400 }) {
|
|
1099
|
-
const elements = []
|
|
1100
|
-
|
|
1101
|
-
// 确保 tags 是数组
|
|
1102
|
-
if (!Array.isArray(tags) || tags.length === 0) {
|
|
1103
|
-
return { success: true, elements: [], type: 'tagCloud', height: 0 }
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
let currentX = x
|
|
1107
|
-
let currentY = y
|
|
1108
|
-
let rowHeight = 0
|
|
1109
|
-
|
|
1110
|
-
for (const tag of tags) {
|
|
1111
|
-
// 确保 tag.text 是字符串
|
|
1112
|
-
const tagText = String(tag.text || '')
|
|
1113
|
-
if (!tagText) continue
|
|
1114
|
-
|
|
1115
|
-
const textWidth = tagText.length * fontSize * 0.6
|
|
1116
|
-
const tagWidth = textWidth + padding * 2
|
|
1117
|
-
const tagHeight = fontSize + padding * 2
|
|
1118
|
-
|
|
1119
|
-
if (currentX + tagWidth > x + maxWidth && currentX > x) {
|
|
1120
|
-
currentX = x
|
|
1121
|
-
currentY += rowHeight + gap
|
|
1122
|
-
rowHeight = 0
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
const tagBg = new paper.Path.Rectangle({
|
|
1126
|
-
point: [currentX, currentY],
|
|
1127
|
-
size: [tagWidth, tagHeight],
|
|
1128
|
-
radius: tagHeight / 2,
|
|
1129
|
-
})
|
|
1130
|
-
tagBg.fillColor = new paper.Color(tag.bgColor || '#e0e7ff')
|
|
1131
|
-
elements.push({ type: 'rectangle', id: tagBg.id })
|
|
1132
|
-
|
|
1133
|
-
// 添加到项目
|
|
1134
|
-
if (project && project.activeLayer) {
|
|
1135
|
-
project.activeLayer.addChild(tagBg)
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const tagTextEl = new paper.PointText({
|
|
1139
|
-
point: [currentX + tagWidth / 2, currentY + tagHeight / 2 + fontSize / 3],
|
|
1140
|
-
content: tagText,
|
|
1141
|
-
fontSize: fontSize,
|
|
1142
|
-
fillColor: new paper.Color(tag.color || '#4338ca'),
|
|
1143
|
-
justification: 'center',
|
|
1144
|
-
})
|
|
1145
|
-
elements.push(tagTextEl)
|
|
1146
|
-
if (project && project.activeLayer) {
|
|
1147
|
-
project.activeLayer.addChild(tagTextEl)
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
currentX += tagWidth + gap
|
|
1151
|
-
rowHeight = Math.max(rowHeight, tagHeight)
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
return { success: true, elements, type: 'tagCloud', height: rowHeight }
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
function createStepperComponent(project, canvas, { x, y, width = 600, steps = [], currentStep = 0, activeColor = '#6366f1', inactiveColor = '#e5e7eb', completedColor = '#22c55e', circleSize = 40 }) {
|
|
1158
|
-
const elements = []
|
|
1159
|
-
const stepWidth = steps.length > 1 ? width / (steps.length - 1) : width
|
|
1160
|
-
const lineY = y + circleSize / 2
|
|
1161
|
-
|
|
1162
|
-
if (steps.length > 1) {
|
|
1163
|
-
elements.push({
|
|
1164
|
-
type: 'line',
|
|
1165
|
-
id: new paper.Path.Line({
|
|
1166
|
-
from: [x + circleSize / 2, lineY],
|
|
1167
|
-
to: [x + width - circleSize / 2, lineY],
|
|
1168
|
-
strokeColor: new paper.Color(inactiveColor),
|
|
1169
|
-
strokeWidth: 2,
|
|
1170
|
-
}).id
|
|
1171
|
-
})
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
for (let i = 0; i < steps.length; i++) {
|
|
1175
|
-
const stepX = steps.length > 1 ? x + i * stepWidth : x
|
|
1176
|
-
let color = inactiveColor
|
|
1177
|
-
if (i < currentStep) color = completedColor
|
|
1178
|
-
else if (i === currentStep) color = activeColor
|
|
1179
|
-
|
|
1180
|
-
const circle = new paper.Path.Circle({
|
|
1181
|
-
center: [stepX + circleSize / 2, lineY],
|
|
1182
|
-
radius: circleSize / 2,
|
|
1183
|
-
})
|
|
1184
|
-
circle.fillColor = new paper.Color(color)
|
|
1185
|
-
elements.push({ type: 'circle', id: circle.id })
|
|
1186
|
-
|
|
1187
|
-
const icon = i < currentStep ? '✓' : String(i + 1)
|
|
1188
|
-
elements.push(new paper.PointText({
|
|
1189
|
-
point: [stepX + circleSize / 2, lineY + circleSize / 6],
|
|
1190
|
-
content: icon,
|
|
1191
|
-
fontSize: 16,
|
|
1192
|
-
fillColor: new paper.Color('#ffffff'),
|
|
1193
|
-
justification: 'center',
|
|
1194
|
-
}))
|
|
1195
|
-
|
|
1196
|
-
elements.push(new paper.PointText({
|
|
1197
|
-
point: [stepX + circleSize / 2, y + circleSize + 20],
|
|
1198
|
-
content: steps[i].title || `Step ${i + 1}`,
|
|
1199
|
-
fontSize: 14,
|
|
1200
|
-
fillColor: new paper.Color(i <= currentStep ? '#1e293b' : '#94a3b8'),
|
|
1201
|
-
justification: 'center',
|
|
1202
|
-
}))
|
|
1203
|
-
|
|
1204
|
-
if (steps[i].description) {
|
|
1205
|
-
elements.push(new paper.PointText({
|
|
1206
|
-
point: [stepX + circleSize / 2, y + circleSize + 38],
|
|
1207
|
-
content: steps[i].description,
|
|
1208
|
-
fontSize: 11,
|
|
1209
|
-
fillColor: new paper.Color('#94a3b8'),
|
|
1210
|
-
justification: 'center',
|
|
1211
|
-
}))
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
return { success: true, elements, type: 'stepper' }
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
function createTimelineComponent(project, canvas, { x, y, width = 500, items = [], lineColor = '#e2e8f0', dotColor = '#6366f1', dotSize = 16, gap = 60 }) {
|
|
1219
|
-
const elements = []
|
|
1220
|
-
|
|
1221
|
-
// 确保 items 是数组
|
|
1222
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
1223
|
-
return { success: true, elements: [], type: 'timeline', height: 0 }
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
const centerX = x + 80
|
|
1227
|
-
const contentX = x + 120
|
|
1228
|
-
|
|
1229
|
-
if (items.length > 1) {
|
|
1230
|
-
const mainLine = new paper.Path.Line({
|
|
1231
|
-
from: [centerX, y + dotSize / 2],
|
|
1232
|
-
to: [centerX, y + (items.length - 1) * gap + dotSize / 2],
|
|
1233
|
-
strokeColor: new paper.Color(lineColor),
|
|
1234
|
-
strokeWidth: 2,
|
|
1235
|
-
})
|
|
1236
|
-
elements.push({ type: 'line', id: mainLine.id })
|
|
1237
|
-
if (project && project.activeLayer) {
|
|
1238
|
-
project.activeLayer.addChild(mainLine)
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
for (let i = 0; i < items.length; i++) {
|
|
1243
|
-
const item = items[i]
|
|
1244
|
-
if (!item) continue
|
|
1245
|
-
|
|
1246
|
-
const itemY = y + i * gap
|
|
1247
|
-
const isActive = item.active !== false
|
|
1248
|
-
|
|
1249
|
-
const dot = new paper.Path.Circle({
|
|
1250
|
-
center: [centerX, itemY + dotSize / 2],
|
|
1251
|
-
radius: dotSize / 2,
|
|
1252
|
-
})
|
|
1253
|
-
dot.fillColor = new paper.Color(isActive ? dotColor : lineColor)
|
|
1254
|
-
elements.push({ type: 'circle', id: dot.id })
|
|
1255
|
-
if (project && project.activeLayer) {
|
|
1256
|
-
project.activeLayer.addChild(dot)
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
if (item.date) {
|
|
1260
|
-
const dateText = new paper.PointText({
|
|
1261
|
-
point: [x + 10, itemY + dotSize / 2 + 5],
|
|
1262
|
-
content: item.date,
|
|
1263
|
-
fontSize: 12,
|
|
1264
|
-
fillColor: new paper.Color('#94a3b8'),
|
|
1265
|
-
justification: 'left',
|
|
1266
|
-
})
|
|
1267
|
-
elements.push(dateText)
|
|
1268
|
-
if (project && project.activeLayer) {
|
|
1269
|
-
project.activeLayer.addChild(dateText)
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
const titleText = new paper.PointText({
|
|
1274
|
-
point: [contentX, itemY + dotSize / 2 + 5],
|
|
1275
|
-
content: item.title || `Event ${i + 1}`,
|
|
1276
|
-
fontSize: 16,
|
|
1277
|
-
fillColor: new paper.Color(isActive ? '#1e293b' : '#94a3b8'),
|
|
1278
|
-
justification: 'left',
|
|
1279
|
-
})
|
|
1280
|
-
elements.push(titleText)
|
|
1281
|
-
if (project && project.activeLayer) {
|
|
1282
|
-
project.activeLayer.addChild(titleText)
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
if (item.description) {
|
|
1286
|
-
const descText = new paper.PointText({
|
|
1287
|
-
point: [contentX, itemY + dotSize / 2 + 28],
|
|
1288
|
-
content: item.description,
|
|
1289
|
-
fontSize: 13,
|
|
1290
|
-
fillColor: new paper.Color('#64748b'),
|
|
1291
|
-
justification: 'left',
|
|
1292
|
-
})
|
|
1293
|
-
elements.push(descText)
|
|
1294
|
-
if (project && project.activeLayer) {
|
|
1295
|
-
project.activeLayer.addChild(descText)
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
return { success: true, elements, type: 'timeline', height: items.length * gap }
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
function createListItemComponent(project, canvas, { x = 0, y = 0, width = 400, icon = '→', title, description, badge, badgeColor = '#6366f1', iconColor = '#6366f1', background = '#ffffff', borderColor = '#e5e7eb', height = 60, radius = 8 }) {
|
|
1304
|
-
const elements = []
|
|
1305
|
-
|
|
1306
|
-
// 添加到项目活动层
|
|
1307
|
-
const addToProject = (item) => {
|
|
1308
|
-
if (project && project.activeLayer) {
|
|
1309
|
-
project.activeLayer.addChild(item)
|
|
1310
|
-
}
|
|
1311
|
-
elements.push(item)
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
const bg = new paper.Path.Rectangle({
|
|
1315
|
-
point: [x, y],
|
|
1316
|
-
size: [width, height],
|
|
1317
|
-
radius: radius,
|
|
1318
|
-
})
|
|
1319
|
-
bg.fillColor = new paper.Color(background)
|
|
1320
|
-
bg.strokeColor = new paper.Color(borderColor)
|
|
1321
|
-
bg.strokeWidth = 1
|
|
1322
|
-
addToProject(bg)
|
|
1323
|
-
|
|
1324
|
-
const iconText = new paper.PointText({
|
|
1325
|
-
point: [x + 15, y + height / 2 + 6],
|
|
1326
|
-
content: icon,
|
|
1327
|
-
fontSize: 20,
|
|
1328
|
-
fillColor: new paper.Color(iconColor),
|
|
1329
|
-
justification: 'center',
|
|
1330
|
-
})
|
|
1331
|
-
addToProject(iconText)
|
|
1332
|
-
|
|
1333
|
-
const titleText = new paper.PointText({
|
|
1334
|
-
point: [x + 50, y + height / 2 - 5],
|
|
1335
|
-
content: title || 'List Item',
|
|
1336
|
-
fontSize: 16,
|
|
1337
|
-
fillColor: new paper.Color('#1e293b'),
|
|
1338
|
-
justification: 'left',
|
|
1339
|
-
})
|
|
1340
|
-
addToProject(titleText)
|
|
1341
|
-
|
|
1342
|
-
if (description) {
|
|
1343
|
-
const descText = new paper.PointText({
|
|
1344
|
-
point: [x + 50, y + height / 2 + 15],
|
|
1345
|
-
content: description,
|
|
1346
|
-
fontSize: 12,
|
|
1347
|
-
fillColor: new paper.Color('#64748b'),
|
|
1348
|
-
justification: 'left',
|
|
1349
|
-
})
|
|
1350
|
-
addToProject(descText)
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
if (badge) {
|
|
1354
|
-
const badgeWidth = badge.length * 10 + 20
|
|
1355
|
-
const badgeX = x + width - badgeWidth - 15
|
|
1356
|
-
const badgeY = y + (height - 24) / 2
|
|
1357
|
-
|
|
1358
|
-
const badgeRect = new paper.Path.Rectangle({
|
|
1359
|
-
point: [badgeX, badgeY],
|
|
1360
|
-
size: [badgeWidth, 24],
|
|
1361
|
-
radius: 12,
|
|
1362
|
-
})
|
|
1363
|
-
badgeRect.fillColor = new paper.Color(badgeColor)
|
|
1364
|
-
addToProject(badgeRect)
|
|
1365
|
-
|
|
1366
|
-
const badgeText = new paper.PointText({
|
|
1367
|
-
point: [badgeX + badgeWidth / 2, badgeY + 16],
|
|
1368
|
-
content: badge,
|
|
1369
|
-
fontSize: 12,
|
|
1370
|
-
fillColor: new paper.Color('#ffffff'),
|
|
1371
|
-
justification: 'center',
|
|
1372
|
-
})
|
|
1373
|
-
addToProject(badgeText)
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
return { success: true, elements, type: 'listItem' }
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
function createNotificationComponent(project, canvas, { x, y, width = 360, type = 'info', title, message, showIcon = true, radius = 12 }) {
|
|
1380
|
-
const config = {
|
|
1381
|
-
success: { icon: '✓', bgColor: '#dcfce7', iconColor: '#22c55e', borderColor: '#22c55e' },
|
|
1382
|
-
warning: { icon: '⚠', bgColor: '#fef9c3', iconColor: '#eab308', borderColor: '#eab308' },
|
|
1383
|
-
error: { icon: '✕', bgColor: '#fee2e2', iconColor: '#ef4444', borderColor: '#ef4444' },
|
|
1384
|
-
info: { icon: 'ℹ', bgColor: '#dbeafe', iconColor: '#3b82f6', borderColor: '#3b82f6' },
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
const c = config[type] || config.info
|
|
1388
|
-
const padding = 16
|
|
1389
|
-
const lineHeight = 22
|
|
1390
|
-
const iconSize = 24
|
|
1391
|
-
const height = padding * 2 + (title ? lineHeight + 8 : 0) + (message ? lineHeight : 0)
|
|
1392
|
-
const elements = []
|
|
1393
|
-
|
|
1394
|
-
const bg = new paper.Path.Rectangle({
|
|
1395
|
-
point: [x, y],
|
|
1396
|
-
size: [width, height],
|
|
1397
|
-
radius: radius,
|
|
1398
|
-
})
|
|
1399
|
-
bg.fillColor = new paper.Color(c.bgColor)
|
|
1400
|
-
bg.strokeColor = new paper.Color(c.borderColor)
|
|
1401
|
-
bg.strokeWidth = 1
|
|
1402
|
-
elements.push({ type: 'rectangle', id: bg.id })
|
|
1403
|
-
|
|
1404
|
-
if (showIcon) {
|
|
1405
|
-
elements.push(new paper.PointText({
|
|
1406
|
-
point: [x + padding + iconSize / 2, y + padding + iconSize / 2 + 6],
|
|
1407
|
-
content: c.icon,
|
|
1408
|
-
fontSize: iconSize,
|
|
1409
|
-
fillColor: new paper.Color(c.iconColor),
|
|
1410
|
-
justification: 'center',
|
|
1411
|
-
}))
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
const textX = showIcon ? x + padding + iconSize + 12 : x + padding
|
|
1415
|
-
let currentY = y + padding
|
|
1416
|
-
|
|
1417
|
-
if (title) {
|
|
1418
|
-
elements.push(new paper.PointText({
|
|
1419
|
-
point: [textX, currentY + 18],
|
|
1420
|
-
content: title,
|
|
1421
|
-
fontSize: 16,
|
|
1422
|
-
fillColor: new paper.Color('#1e293b'),
|
|
1423
|
-
justification: 'left',
|
|
1424
|
-
}))
|
|
1425
|
-
currentY += lineHeight + 8
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
if (message) {
|
|
1429
|
-
elements.push(new paper.PointText({
|
|
1430
|
-
point: [textX, currentY + 16],
|
|
1431
|
-
content: message,
|
|
1432
|
-
fontSize: 14,
|
|
1433
|
-
fillColor: new paper.Color('#475569'),
|
|
1434
|
-
justification: 'left',
|
|
1435
|
-
}))
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
return { success: true, elements, type: 'notification' }
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
/**
|
|
1442
|
-
* 创建图片框组件
|
|
1443
|
-
*/
|
|
1444
|
-
async function createImageFrameComponent(project, canvas, {
|
|
1445
|
-
src,
|
|
1446
|
-
x,
|
|
1447
|
-
y,
|
|
1448
|
-
width,
|
|
1449
|
-
height,
|
|
1450
|
-
borderColor = '#ffffff',
|
|
1451
|
-
borderWidth = 3,
|
|
1452
|
-
outerColor = '#1a1a2e',
|
|
1453
|
-
outerWidth = 6,
|
|
1454
|
-
shadowBlur = 0,
|
|
1455
|
-
shadowOffsetX = 0,
|
|
1456
|
-
shadowOffsetY = 0,
|
|
1457
|
-
shadowColor = 'rgba(0,0,0,0.3)',
|
|
1458
|
-
radius = 0,
|
|
1459
|
-
overlayColor,
|
|
1460
|
-
overlayOpacity = 0,
|
|
1461
|
-
fit = 'cover'
|
|
1462
|
-
}) {
|
|
1463
|
-
const elements = []
|
|
1464
|
-
|
|
1465
|
-
// 绘制外边框(装饰层)
|
|
1466
|
-
if (outerWidth > 0) {
|
|
1467
|
-
const outerBg = new paper.Path.Rectangle({
|
|
1468
|
-
point: [x - outerWidth, y - outerWidth],
|
|
1469
|
-
size: [width + outerWidth * 2, height + outerWidth * 2],
|
|
1470
|
-
radius: radius + outerWidth
|
|
1471
|
-
})
|
|
1472
|
-
outerBg.fillColor = new paper.Color(outerColor)
|
|
1473
|
-
elements.push({ type: 'rectangle', id: outerBg.id })
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
// 绘制内边框
|
|
1477
|
-
if (borderWidth > 0) {
|
|
1478
|
-
const innerBg = new paper.Path.Rectangle({
|
|
1479
|
-
point: [x - borderWidth, y - borderWidth],
|
|
1480
|
-
size: [width + borderWidth * 2, height + borderWidth * 2],
|
|
1481
|
-
radius: radius + borderWidth
|
|
1482
|
-
})
|
|
1483
|
-
innerBg.fillColor = new paper.Color(borderColor)
|
|
1484
|
-
elements.push({ type: 'rectangle', id: innerBg.id })
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
// 加载并绘制图片
|
|
1488
|
-
const image = await _loadImage(src)
|
|
1489
|
-
const imgWidth = image.width
|
|
1490
|
-
const imgHeight = image.height
|
|
1491
|
-
const imgRatio = imgWidth / imgHeight
|
|
1492
|
-
const boxRatio = width / height
|
|
1493
|
-
|
|
1494
|
-
let drawX = x, drawY = y, drawW = width, drawH = height
|
|
1495
|
-
|
|
1496
|
-
if (fit === 'cover') {
|
|
1497
|
-
if (imgRatio > boxRatio) {
|
|
1498
|
-
drawH = height
|
|
1499
|
-
drawW = height * imgRatio
|
|
1500
|
-
drawX = x - (drawW - width) / 2
|
|
1501
|
-
} else {
|
|
1502
|
-
drawW = width
|
|
1503
|
-
drawH = width / imgRatio
|
|
1504
|
-
drawY = y - (drawH - height) / 2
|
|
1505
|
-
}
|
|
1506
|
-
} else if (fit === 'contain') {
|
|
1507
|
-
if (imgRatio > boxRatio) {
|
|
1508
|
-
drawW = width
|
|
1509
|
-
drawH = width / imgRatio
|
|
1510
|
-
drawY = y + (height - drawH) / 2
|
|
1511
|
-
} else {
|
|
1512
|
-
drawH = height
|
|
1513
|
-
drawW = height * imgRatio
|
|
1514
|
-
drawX = x + (width - drawW) / 2
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
// 创建裁剪区域
|
|
1519
|
-
const clipPath = new paper.Path.Rectangle({
|
|
1520
|
-
point: [x, y],
|
|
1521
|
-
size: [width, height],
|
|
1522
|
-
radius: radius
|
|
1523
|
-
})
|
|
1524
|
-
|
|
1525
|
-
// 添加阴影
|
|
1526
|
-
if (shadowBlur > 0) {
|
|
1527
|
-
const shadowRect = new paper.Path.Rectangle({
|
|
1528
|
-
point: [x + shadowOffsetX, y + shadowOffsetY],
|
|
1529
|
-
size: [width, height],
|
|
1530
|
-
radius: radius
|
|
1531
|
-
})
|
|
1532
|
-
shadowRect.fillColor = new paper.Color(shadowColor)
|
|
1533
|
-
shadowRect.opacity = shadowBlur / 50
|
|
1534
|
-
shadowRect.shadowColor = new paper.Color(shadowColor)
|
|
1535
|
-
shadowRect.shadowBlur = shadowBlur
|
|
1536
|
-
elements.push({ type: 'rectangle', id: shadowRect.id })
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
// 绘制图片
|
|
1540
|
-
const raster = new paper.Raster({
|
|
1541
|
-
source: src,
|
|
1542
|
-
position: [drawX + drawW / 2, drawY + drawH / 2]
|
|
1543
|
-
})
|
|
1544
|
-
|
|
1545
|
-
await new Promise((resolve) => {
|
|
1546
|
-
raster.onLoad = resolve
|
|
1547
|
-
})
|
|
1548
|
-
|
|
1549
|
-
raster.size = new paper.Size(drawW, drawH)
|
|
1550
|
-
raster.position = new paper.Point(drawX + drawW / 2, drawY + drawH / 2)
|
|
1551
|
-
|
|
1552
|
-
// 应用裁剪
|
|
1553
|
-
clipPath.clipMask = true
|
|
1554
|
-
|
|
1555
|
-
elements.push({ type: 'raster', id: raster.id })
|
|
1556
|
-
|
|
1557
|
-
// 叠加颜色
|
|
1558
|
-
if (overlayColor && overlayOpacity > 0) {
|
|
1559
|
-
const overlay = new paper.Path.Rectangle({
|
|
1560
|
-
point: [x, y],
|
|
1561
|
-
size: [width, height],
|
|
1562
|
-
radius: radius
|
|
1563
|
-
})
|
|
1564
|
-
overlay.fillColor = new paper.Color(overlayColor)
|
|
1565
|
-
overlay.opacity = overlayOpacity
|
|
1566
|
-
elements.push({ type: 'rectangle', id: overlay.id })
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
return { success: true, elements, type: 'imageFrame' }
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
// 辅助函数:加载图片
|
|
1573
|
-
async function _loadImage(src) {
|
|
1574
|
-
return new Promise((resolve, reject) => {
|
|
1575
|
-
const img = new Image()
|
|
1576
|
-
img.crossOrigin = 'anonymous'
|
|
1577
|
-
img.onload = () => resolve(img)
|
|
1578
|
-
img.onerror = () => {
|
|
1579
|
-
if (src.startsWith('data:')) {
|
|
1580
|
-
img.src = src
|
|
1581
|
-
} else {
|
|
1582
|
-
reject(new Error(`Failed to load image: ${src}`))
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
img.src = src
|
|
1586
|
-
})
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
/**
|
|
1590
|
-
* 创建分栏布局组件
|
|
1591
|
-
*/
|
|
1592
|
-
function createColumnsComponent(project, canvas, {
|
|
1593
|
-
x,
|
|
1594
|
-
y,
|
|
1595
|
-
width,
|
|
1596
|
-
height,
|
|
1597
|
-
columns = 2,
|
|
1598
|
-
gap = 20,
|
|
1599
|
-
background,
|
|
1600
|
-
borderColor,
|
|
1601
|
-
borderWidth = 1,
|
|
1602
|
-
radius = 0,
|
|
1603
|
-
direction = 'horizontal',
|
|
1604
|
-
align = 'top'
|
|
1605
|
-
}) {
|
|
1606
|
-
const elements = []
|
|
1607
|
-
|
|
1608
|
-
// 计算每列宽度
|
|
1609
|
-
const totalGap = gap * (columns - 1)
|
|
1610
|
-
const columnWidth = (width - totalGap) / columns
|
|
1611
|
-
|
|
1612
|
-
// 绘制背景
|
|
1613
|
-
if (background) {
|
|
1614
|
-
const bg = new paper.Path.Rectangle({
|
|
1615
|
-
point: [x, y],
|
|
1616
|
-
size: [width, height],
|
|
1617
|
-
radius: radius
|
|
1618
|
-
})
|
|
1619
|
-
bg.fillColor = new paper.Color(background)
|
|
1620
|
-
elements.push({ type: 'rectangle', id: bg.id })
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// 绘制边框
|
|
1624
|
-
if (borderColor && borderWidth > 0) {
|
|
1625
|
-
const border = new paper.Path.Rectangle({
|
|
1626
|
-
point: [x, y],
|
|
1627
|
-
size: [width, height],
|
|
1628
|
-
radius: radius
|
|
1629
|
-
})
|
|
1630
|
-
border.fillColor = new paper.Color('transparent')
|
|
1631
|
-
border.strokeColor = new paper.Color(borderColor)
|
|
1632
|
-
border.strokeWidth = borderWidth
|
|
1633
|
-
elements.push({ type: 'rectangle', id: border.id })
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// 生成分割线
|
|
1637
|
-
for (let i = 1; i < columns; i++) {
|
|
1638
|
-
const lineX = x + columnWidth * i + gap * (i - 1) + gap / 2
|
|
1639
|
-
const line = new paper.Path.Line({
|
|
1640
|
-
from: [lineX, y + 20],
|
|
1641
|
-
to: [lineX, y + height - 20]
|
|
1642
|
-
})
|
|
1643
|
-
line.strokeColor = new paper.Color('#e0e0e0')
|
|
1644
|
-
line.strokeWidth = 1
|
|
1645
|
-
elements.push({ type: 'line', id: line.id })
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
// 返回列位置信息
|
|
1649
|
-
const columnPositions = []
|
|
1650
|
-
for (let i = 0; i < columns; i++) {
|
|
1651
|
-
const colX = x + (columnWidth + gap) * i
|
|
1652
|
-
const colY = align === 'center' ? y + (height - height) / 2 : align === 'bottom' ? y + height - height : y
|
|
1653
|
-
|
|
1654
|
-
columnPositions.push({
|
|
1655
|
-
index: i,
|
|
1656
|
-
x: colX,
|
|
1657
|
-
y: colY,
|
|
1658
|
-
width: columnWidth,
|
|
1659
|
-
height: height,
|
|
1660
|
-
centerX: colX + columnWidth / 2,
|
|
1661
|
-
centerY: colY + height / 2
|
|
1662
|
-
})
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
return {
|
|
1666
|
-
success: true,
|
|
1667
|
-
elements,
|
|
1668
|
-
columnPositions,
|
|
1669
|
-
columnWidth,
|
|
1670
|
-
totalWidth: width,
|
|
1671
|
-
totalHeight: height,
|
|
1672
|
-
type: 'columns'
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
/**
|
|
1677
|
-
* 创建网格布局组件
|
|
1678
|
-
*/
|
|
1679
|
-
function createGridComponent(project, canvas, {
|
|
1680
|
-
x,
|
|
1681
|
-
y,
|
|
1682
|
-
width,
|
|
1683
|
-
height,
|
|
1684
|
-
columns = 3,
|
|
1685
|
-
rows = 2,
|
|
1686
|
-
gapX = 20,
|
|
1687
|
-
gapY = 20,
|
|
1688
|
-
background,
|
|
1689
|
-
borderColor,
|
|
1690
|
-
borderWidth = 1,
|
|
1691
|
-
radius = 0,
|
|
1692
|
-
direction = 'row'
|
|
1693
|
-
}) {
|
|
1694
|
-
const elements = []
|
|
1695
|
-
|
|
1696
|
-
// 计算单元格尺寸
|
|
1697
|
-
const totalGapX = gapX * (columns - 1)
|
|
1698
|
-
const totalGapY = gapY * (rows - 1)
|
|
1699
|
-
const cellWidth = (width - totalGapX) / columns
|
|
1700
|
-
const cellHeight = (height - totalGapY) / rows
|
|
1701
|
-
|
|
1702
|
-
// 绘制背景
|
|
1703
|
-
if (background) {
|
|
1704
|
-
const bg = new paper.Path.Rectangle({
|
|
1705
|
-
point: [x, y],
|
|
1706
|
-
size: [width, height],
|
|
1707
|
-
radius: radius
|
|
1708
|
-
})
|
|
1709
|
-
bg.fillColor = new paper.Color(background)
|
|
1710
|
-
elements.push({ type: 'rectangle', id: bg.id })
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
// 绘制边框
|
|
1714
|
-
if (borderColor && borderWidth > 0) {
|
|
1715
|
-
const border = new paper.Path.Rectangle({
|
|
1716
|
-
point: [x, y],
|
|
1717
|
-
size: [width, height],
|
|
1718
|
-
radius: radius
|
|
1719
|
-
})
|
|
1720
|
-
border.fillColor = new paper.Color('transparent')
|
|
1721
|
-
border.strokeColor = new paper.Color(borderColor)
|
|
1722
|
-
border.strokeWidth = borderWidth
|
|
1723
|
-
elements.push({ type: 'rectangle', id: border.id })
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
// 生成网格线(可选,这里不绘制让用户自己控制)
|
|
1727
|
-
|
|
1728
|
-
// 生成网格位置信息
|
|
1729
|
-
const cellPositions = []
|
|
1730
|
-
const totalCells = columns * rows
|
|
1731
|
-
|
|
1732
|
-
for (let i = 0; i < totalCells; i++) {
|
|
1733
|
-
let col, row
|
|
1734
|
-
|
|
1735
|
-
if (direction === 'row') {
|
|
1736
|
-
col = i % columns
|
|
1737
|
-
row = Math.floor(i / columns)
|
|
1738
|
-
} else {
|
|
1739
|
-
row = i % rows
|
|
1740
|
-
col = Math.floor(i / rows)
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
const cellX = x + col * (cellWidth + gapX)
|
|
1744
|
-
const cellY = y + row * (cellHeight + gapY)
|
|
1745
|
-
|
|
1746
|
-
cellPositions.push({
|
|
1747
|
-
index: i,
|
|
1748
|
-
column: col,
|
|
1749
|
-
row: row,
|
|
1750
|
-
x: cellX,
|
|
1751
|
-
y: cellY,
|
|
1752
|
-
width: cellWidth,
|
|
1753
|
-
height: cellHeight,
|
|
1754
|
-
centerX: cellX + cellWidth / 2,
|
|
1755
|
-
centerY: cellY + cellHeight / 2
|
|
1756
|
-
})
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
// 返回网格布局信息
|
|
1760
|
-
return {
|
|
1761
|
-
success: true,
|
|
1762
|
-
elements,
|
|
1763
|
-
cellPositions,
|
|
1764
|
-
cellWidth,
|
|
1765
|
-
cellHeight,
|
|
1766
|
-
columns,
|
|
1767
|
-
rows,
|
|
1768
|
-
totalCells,
|
|
1769
|
-
totalWidth: width,
|
|
1770
|
-
totalHeight: height,
|
|
1771
|
-
type: 'grid'
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
/**
|
|
1777
|
-
* 创建星形组件
|
|
1778
|
-
*/
|
|
1779
|
-
function createStarComponent(project, canvas, {
|
|
1780
|
-
cx, cy, points = 5, innerRadius: providedInnerRadius, outerRadius,
|
|
1781
|
-
fill, stroke, strokeWidth = 1, opacity = 1, rotation = 0
|
|
1782
|
-
}) {
|
|
1783
|
-
const innerRadius = providedInnerRadius || outerRadius * 0.4
|
|
1784
|
-
const path = new paper.Path()
|
|
1785
|
-
const angleStep = Math.PI / points
|
|
1786
|
-
|
|
1787
|
-
for (let i = 0; i < points * 2; i++) {
|
|
1788
|
-
const radius = i % 2 === 0 ? outerRadius : innerRadius
|
|
1789
|
-
const angle = i * angleStep - Math.PI / 2 + (rotation * Math.PI / 180)
|
|
1790
|
-
const x = cx + radius * Math.cos(angle)
|
|
1791
|
-
const y = cy + radius * Math.sin(angle)
|
|
1792
|
-
if (i === 0) path.moveTo(x, y)
|
|
1793
|
-
else path.lineTo(x, y)
|
|
1794
|
-
}
|
|
1795
|
-
path.closePath()
|
|
1796
|
-
|
|
1797
|
-
if (fill) path.fillColor = new paper.Color(fill)
|
|
1798
|
-
if (stroke) { path.strokeColor = new paper.Color(stroke); path.strokeWidth = strokeWidth }
|
|
1799
|
-
path.opacity = opacity
|
|
1800
|
-
if (project && project.activeLayer) project.activeLayer.addChild(path)
|
|
1801
|
-
|
|
1802
|
-
return { success: true, elements: [{ type: 'path', id: path.id }], type: 'star' }
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
/**
|
|
1806
|
-
* 创建箭头组件
|
|
1807
|
-
*/
|
|
1808
|
-
function createArrowComponent(project, canvas, {
|
|
1809
|
-
x1, y1, x2, y2, color = '#333333', strokeWidth = 2, headSize = 12, style = 'solid', direction = 'end'
|
|
1810
|
-
}) {
|
|
1811
|
-
const elements = []
|
|
1812
|
-
const angle = Math.atan2(y2 - y1, x2 - x1)
|
|
1813
|
-
|
|
1814
|
-
const line = new paper.Path.Line({ from: [x1, y1], to: [x2, y2] })
|
|
1815
|
-
line.strokeColor = new paper.Color(color)
|
|
1816
|
-
line.strokeWidth = strokeWidth
|
|
1817
|
-
if (style === 'dashed') line.dashArray = [10, 5]
|
|
1818
|
-
elements.push({ type: 'line', id: line.id })
|
|
1819
|
-
|
|
1820
|
-
if (direction === 'end' || direction === 'both') {
|
|
1821
|
-
const arrowHead = new paper.Path()
|
|
1822
|
-
arrowHead.moveTo(x2, y2)
|
|
1823
|
-
arrowHead.lineTo(x2 + headSize * Math.cos(angle + Math.PI * 0.8), y2 + headSize * Math.sin(angle + Math.PI * 0.8))
|
|
1824
|
-
arrowHead.moveTo(x2, y2)
|
|
1825
|
-
arrowHead.lineTo(x2 + headSize * Math.cos(angle - Math.PI * 0.8), y2 + headSize * Math.sin(angle - Math.PI * 0.8))
|
|
1826
|
-
arrowHead.strokeColor = new paper.Color(color)
|
|
1827
|
-
arrowHead.strokeWidth = strokeWidth
|
|
1828
|
-
arrowHead.strokeCap = 'round'
|
|
1829
|
-
elements.push({ type: 'path', id: arrowHead.id })
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
if (direction === 'start' || direction === 'both') {
|
|
1833
|
-
const startAngle = angle + Math.PI
|
|
1834
|
-
const arrowHead = new paper.Path()
|
|
1835
|
-
arrowHead.moveTo(x1, y1)
|
|
1836
|
-
arrowHead.lineTo(x1 + headSize * Math.cos(startAngle + Math.PI * 0.8), y1 + headSize * Math.sin(startAngle + Math.PI * 0.8))
|
|
1837
|
-
arrowHead.moveTo(x1, y1)
|
|
1838
|
-
arrowHead.lineTo(x1 + headSize * Math.cos(startAngle - Math.PI * 0.8), y1 + headSize * Math.sin(startAngle - Math.PI * 0.8))
|
|
1839
|
-
arrowHead.strokeColor = new paper.Color(color)
|
|
1840
|
-
arrowHead.strokeWidth = strokeWidth
|
|
1841
|
-
arrowHead.strokeCap = 'round'
|
|
1842
|
-
elements.push({ type: 'path', id: arrowHead.id })
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
return { success: true, elements, type: 'arrow' }
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
/**
|
|
1849
|
-
* 创建环形进度条组件
|
|
1850
|
-
*/
|
|
1851
|
-
function createProgressCircleComponent(project, canvas, {
|
|
1852
|
-
cx, cy, radius, value, strokeWidth = 10, trackColor = '#e0e0e0',
|
|
1853
|
-
fillColor = '#3b82f6', backgroundColor, showLabel = true, labelColor, startAngle = -90
|
|
1854
|
-
}) {
|
|
1855
|
-
const elements = []
|
|
1856
|
-
|
|
1857
|
-
if (backgroundColor) {
|
|
1858
|
-
const bgCircle = new paper.Path.Circle({ center: [cx, cy], radius: radius })
|
|
1859
|
-
bgCircle.fillColor = new paper.Color(backgroundColor)
|
|
1860
|
-
elements.push({ type: 'path', id: bgCircle.id })
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
const trackCircle = new paper.Path.Circle({ center: [cx, cy], radius: radius })
|
|
1864
|
-
trackCircle.fillColor = new paper.Color('transparent')
|
|
1865
|
-
trackCircle.strokeColor = new paper.Color(trackColor)
|
|
1866
|
-
trackCircle.strokeWidth = strokeWidth
|
|
1867
|
-
elements.push({ type: 'path', id: trackCircle.id })
|
|
1868
|
-
|
|
1869
|
-
if (value > 0) {
|
|
1870
|
-
const endAngle = startAngle + (value / 100) * 360
|
|
1871
|
-
const startRad = startAngle * Math.PI / 180
|
|
1872
|
-
const endRad = endAngle * Math.PI / 180
|
|
1873
|
-
|
|
1874
|
-
const arc = new paper.Path()
|
|
1875
|
-
arc.moveTo(cx + radius * Math.cos(startRad), cy + radius * Math.sin(startRad))
|
|
1876
|
-
arc.arcTo([cx, cy], radius, endRad - startRad)
|
|
1877
|
-
arc.strokeColor = new paper.Color(fillColor)
|
|
1878
|
-
arc.strokeWidth = strokeWidth
|
|
1879
|
-
arc.strokeCap = 'round'
|
|
1880
|
-
elements.push({ type: 'path', id: arc.id })
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
if (showLabel) {
|
|
1884
|
-
const textColor = labelColor || fillColor
|
|
1885
|
-
const label = new paper.PointText({
|
|
1886
|
-
point: [cx, cy + 6],
|
|
1887
|
-
content: `${Math.round(value)}%`,
|
|
1888
|
-
fontSize: radius * 0.4,
|
|
1889
|
-
fillColor: new paper.Color(textColor),
|
|
1890
|
-
justification: 'center',
|
|
1891
|
-
fontWeight: 'bold'
|
|
1892
|
-
})
|
|
1893
|
-
elements.push({ type: 'text', id: label.id })
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
return { success: true, elements, type: 'progressCircle' }
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
/**
|
|
1900
|
-
* 创建 Chip 标签组件
|
|
1901
|
-
*/
|
|
1902
|
-
function createChipComponent(project, canvas, {
|
|
1903
|
-
x, y, text, background = '#e0e0e0', color = '#333333', borderColor,
|
|
1904
|
-
fontSize = 12, padding = 12, radius = 16, icon
|
|
1905
|
-
}) {
|
|
1906
|
-
const elements = []
|
|
1907
|
-
const textWidth = text.length * fontSize * 0.6
|
|
1908
|
-
const iconWidth = icon ? fontSize : 0
|
|
1909
|
-
const totalWidth = padding * 2 + textWidth + iconWidth + 4
|
|
1910
|
-
const height = fontSize + padding * 2
|
|
1911
|
-
const rectX = x - totalWidth / 2
|
|
1912
|
-
const rectY = y - height / 2
|
|
1913
|
-
|
|
1914
|
-
const bg = new paper.Path.Rectangle({ point: [rectX, rectY], size: [totalWidth, height], radius: radius })
|
|
1915
|
-
bg.fillColor = new paper.Color(background)
|
|
1916
|
-
if (borderColor) { bg.strokeColor = new paper.Color(borderColor); bg.strokeWidth = 1 }
|
|
1917
|
-
elements.push({ type: 'path', id: bg.id })
|
|
1918
|
-
|
|
1919
|
-
if (icon) {
|
|
1920
|
-
const iconText = new paper.PointText({
|
|
1921
|
-
point: [rectX + padding + iconWidth / 2, y + fontSize / 3],
|
|
1922
|
-
content: icon,
|
|
1923
|
-
fontSize: fontSize + 2,
|
|
1924
|
-
fillColor: new paper.Color(color),
|
|
1925
|
-
justification: 'center'
|
|
1926
|
-
})
|
|
1927
|
-
elements.push({ type: 'text', id: iconText.id })
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
|
-
const textX = icon ? rectX + padding + iconWidth + 4 + textWidth / 2 : x
|
|
1931
|
-
const label = new paper.PointText({
|
|
1932
|
-
point: [textX, y + fontSize / 3],
|
|
1933
|
-
content: text,
|
|
1934
|
-
fontSize: fontSize,
|
|
1935
|
-
fillColor: new paper.Color(color),
|
|
1936
|
-
justification: 'center'
|
|
1937
|
-
})
|
|
1938
|
-
elements.push({ type: 'text', id: label.id })
|
|
1939
|
-
|
|
1940
|
-
return { success: true, elements, width: totalWidth, height, type: 'chip' }
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
/**
|
|
1944
|
-
* 创建图表组件
|
|
1945
|
-
*/
|
|
1946
|
-
function createChartComponent(project, canvas, {
|
|
1947
|
-
type = 'bar', x, y, width, height, data = [], barColor = '#3b82f6',
|
|
1948
|
-
showLabels = true, showValues = true, barGap = 4
|
|
1949
|
-
}) {
|
|
1950
|
-
const elements = []
|
|
1951
|
-
|
|
1952
|
-
if (type === 'bar' && data.length > 0) {
|
|
1953
|
-
const maxValue = Math.max(...data.map(d => d.value))
|
|
1954
|
-
const barCount = data.length
|
|
1955
|
-
const totalGap = barGap * (barCount - 1)
|
|
1956
|
-
const barWidth = (width - totalGap) / barCount
|
|
1957
|
-
const labelHeight = showLabels ? 24 : 0
|
|
1958
|
-
const valueHeight = showValues ? 20 : 0
|
|
1959
|
-
const chartHeight = height - labelHeight - valueHeight - 10
|
|
1960
|
-
|
|
1961
|
-
data.forEach((item, index) => {
|
|
1962
|
-
const barHeight = (item.value / maxValue) * chartHeight
|
|
1963
|
-
const barX = x + index * (barWidth + barGap)
|
|
1964
|
-
const barY = y + height - labelHeight - valueHeight - barHeight - 5
|
|
1965
|
-
const color = item.color || barColor
|
|
1966
|
-
|
|
1967
|
-
const bar = new paper.Path.Rectangle({
|
|
1968
|
-
point: [barX, barY],
|
|
1969
|
-
size: [barWidth, barHeight],
|
|
1970
|
-
radius: [4, 4, 0, 0]
|
|
1971
|
-
})
|
|
1972
|
-
bar.fillColor = new paper.Color(color)
|
|
1973
|
-
elements.push({ type: 'path', id: bar.id })
|
|
1974
|
-
|
|
1975
|
-
if (showValues) {
|
|
1976
|
-
const valueText = new paper.PointText({
|
|
1977
|
-
point: [barX + barWidth / 2, barY - 8],
|
|
1978
|
-
content: String(item.value),
|
|
1979
|
-
fontSize: 12,
|
|
1980
|
-
fillColor: new paper.Color('#666666'),
|
|
1981
|
-
justification: 'center'
|
|
1982
|
-
})
|
|
1983
|
-
elements.push({ type: 'text', id: valueText.id })
|
|
1984
|
-
}
|
|
1985
|
-
|
|
1986
|
-
if (showLabels) {
|
|
1987
|
-
const labelText = new paper.PointText({
|
|
1988
|
-
point: [barX + barWidth / 2, y + height - 8],
|
|
1989
|
-
content: item.label || '',
|
|
1990
|
-
fontSize: 11,
|
|
1991
|
-
fillColor: new paper.Color('#333333'),
|
|
1992
|
-
justification: 'center'
|
|
1993
|
-
})
|
|
1994
|
-
elements.push({ type: 'text', id: labelText.id })
|
|
1995
|
-
}
|
|
1996
|
-
})
|
|
1997
|
-
} else if (type === 'pie' && data.length > 0) {
|
|
1998
|
-
const cx = x + width / 2
|
|
1999
|
-
const cy = y + height / 2
|
|
2000
|
-
const radius = Math.min(width, height) / 2 - 10
|
|
2001
|
-
const total = data.reduce((sum, d) => sum + d.value, 0)
|
|
2002
|
-
let currentAngle = -90
|
|
2003
|
-
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16']
|
|
2004
|
-
|
|
2005
|
-
data.forEach((item, index) => {
|
|
2006
|
-
const percentage = item.value / total
|
|
2007
|
-
const endAngle = currentAngle + percentage * 360
|
|
2008
|
-
const path = new paper.Path()
|
|
2009
|
-
path.moveTo(cx, cy)
|
|
2010
|
-
path.arc([cx, cy], radius, currentAngle * Math.PI / 180, endAngle * Math.PI / 180)
|
|
2011
|
-
path.closePath()
|
|
2012
|
-
path.fillColor = new paper.Color(item.color || colors[index % colors.length])
|
|
2013
|
-
elements.push({ type: 'path', id: path.id })
|
|
2014
|
-
|
|
2015
|
-
if (showLabels && percentage > 0.05) {
|
|
2016
|
-
const midAngle = (currentAngle + endAngle) / 2
|
|
2017
|
-
const midRad = midAngle * Math.PI / 180
|
|
2018
|
-
const labelX = cx + radius * 0.7 * Math.cos(midRad)
|
|
2019
|
-
const labelY = cy + radius * 0.7 * Math.sin(midRad)
|
|
2020
|
-
const labelText = new paper.PointText({
|
|
2021
|
-
point: [labelX, labelY + 4],
|
|
2022
|
-
content: `${Math.round(percentage * 100)}%`,
|
|
2023
|
-
fontSize: 11,
|
|
2024
|
-
fillColor: new paper.Color('#ffffff'),
|
|
2025
|
-
justification: 'center',
|
|
2026
|
-
fontWeight: 'bold'
|
|
2027
|
-
})
|
|
2028
|
-
elements.push({ type: 'text', id: labelText.id })
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
currentAngle = endAngle
|
|
2032
|
-
})
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
return { success: true, elements, type: 'chart' }
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
/**
|
|
2039
|
-
* 创建水印组件
|
|
2040
|
-
*/
|
|
2041
|
-
function createWatermarkComponent(project, canvas, {
|
|
2042
|
-
text, cx, cy, color = 'rgba(0,0,0,0.1)', fontSize = 48,
|
|
2043
|
-
fontFamily = 'sans-serif', opacity = 0.1, rotation = 0, align = 'center'
|
|
2044
|
-
}) {
|
|
2045
|
-
const label = new paper.PointText({
|
|
2046
|
-
point: [cx, cy],
|
|
2047
|
-
content: text,
|
|
2048
|
-
fontSize: fontSize,
|
|
2049
|
-
fontFamily: fontFamily,
|
|
2050
|
-
fillColor: new paper.Color(color),
|
|
2051
|
-
justification: align,
|
|
2052
|
-
opacity: opacity
|
|
2053
|
-
})
|
|
2054
|
-
|
|
2055
|
-
if (rotation !== 0) {
|
|
2056
|
-
label.rotate(rotation, new paper.Point(cx, cy))
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
return { success: true, elements: [{ type: 'text', id: label.id }], type: 'watermark' }
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
/**
|
|
2063
|
-
* 创建表格组件
|
|
2064
|
-
*/
|
|
2065
|
-
function createTableComponent(project, canvas, {
|
|
2066
|
-
x, y, width, columns = [], rows = [], rowHeight = 36,
|
|
2067
|
-
headerBg = '#f0f0f0', headerColor = '#333333', borderColor = '#e0e0e0',
|
|
2068
|
-
cellColor = '#333333', fontSize = 12, headerFontSize = 13, striped = true, stripeColor = '#fafafa'
|
|
2069
|
-
}) {
|
|
2070
|
-
const elements = []
|
|
2071
|
-
// 确保 columns 是数组
|
|
2072
|
-
if (!Array.isArray(columns) || columns.length === 0) {
|
|
2073
|
-
return { success: true, elements, type: 'table' }
|
|
2074
|
-
}
|
|
2075
|
-
// 确保 rows 是数组
|
|
2076
|
-
if (!Array.isArray(rows)) {
|
|
2077
|
-
rows = []
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
const totalHeight = rowHeight * (rows.length + 1)
|
|
2081
|
-
|
|
2082
|
-
const outerBorder = new paper.Path.Rectangle({ point: [x, y], size: [width, totalHeight] })
|
|
2083
|
-
outerBorder.fillColor = new paper.Color('transparent')
|
|
2084
|
-
outerBorder.strokeColor = new paper.Color(borderColor)
|
|
2085
|
-
outerBorder.strokeWidth = 1
|
|
2086
|
-
elements.push({ type: 'path', id: outerBorder.id })
|
|
2087
|
-
|
|
2088
|
-
const headerBgRect = new paper.Path.Rectangle({ point: [x, y], size: [width, rowHeight] })
|
|
2089
|
-
headerBgRect.fillColor = new paper.Color(headerBg)
|
|
2090
|
-
headerBgRect.strokeColor = new paper.Color(borderColor)
|
|
2091
|
-
headerBgRect.strokeWidth = 0.5
|
|
2092
|
-
elements.push({ type: 'path', id: headerBgRect.id })
|
|
2093
|
-
|
|
2094
|
-
let currentX = x
|
|
2095
|
-
columns.forEach((col, index) => {
|
|
2096
|
-
const colWidth = col.width || (width / columns.length)
|
|
2097
|
-
if (index > 0) {
|
|
2098
|
-
const line = new paper.Path.Line({ from: [currentX, y], to: [currentX, y + totalHeight] })
|
|
2099
|
-
line.strokeColor = new paper.Color(borderColor)
|
|
2100
|
-
line.strokeWidth = 0.5
|
|
2101
|
-
elements.push({ type: 'line', id: line.id })
|
|
2102
|
-
}
|
|
2103
|
-
const headerText = new paper.PointText({
|
|
2104
|
-
point: [currentX + colWidth / 2, y + rowHeight / 2 + fontSize / 3],
|
|
2105
|
-
content: col.title || '',
|
|
2106
|
-
fontSize: headerFontSize,
|
|
2107
|
-
fillColor: new paper.Color(headerColor),
|
|
2108
|
-
justification: col.align || 'center',
|
|
2109
|
-
fontWeight: 'bold'
|
|
2110
|
-
})
|
|
2111
|
-
elements.push({ type: 'text', id: headerText.id })
|
|
2112
|
-
currentX += colWidth
|
|
2113
|
-
})
|
|
2114
|
-
|
|
2115
|
-
rows.forEach((row, rowIndex) => {
|
|
2116
|
-
const rowY = y + rowHeight * (rowIndex + 1)
|
|
2117
|
-
if (striped && rowIndex % 2 === 1) {
|
|
2118
|
-
const stripeBg = new paper.Path.Rectangle({ point: [x, rowY], size: [width, rowHeight] })
|
|
2119
|
-
stripeBg.fillColor = new paper.Color(stripeColor)
|
|
2120
|
-
stripeBg.strokeColor = new paper.Color(borderColor)
|
|
2121
|
-
stripeBg.strokeWidth = 0.5
|
|
2122
|
-
elements.push({ type: 'path', id: stripeBg.id })
|
|
2123
|
-
}
|
|
2124
|
-
const rowLine = new paper.Path.Line({ from: [x, rowY], to: [x + width, rowY] })
|
|
2125
|
-
rowLine.strokeColor = new paper.Color(borderColor)
|
|
2126
|
-
rowLine.strokeWidth = 0.5
|
|
2127
|
-
elements.push({ type: 'line', id: rowLine.id })
|
|
2128
|
-
|
|
2129
|
-
let cellX = x
|
|
2130
|
-
columns.forEach((col, colIndex) => {
|
|
2131
|
-
const colWidth = col.width || (width / columns.length)
|
|
2132
|
-
const cellValue = row[colIndex] || ''
|
|
2133
|
-
const cellText = new paper.PointText({
|
|
2134
|
-
point: [cellX + colWidth / 2, rowY + rowHeight / 2 + fontSize / 3],
|
|
2135
|
-
content: String(cellValue),
|
|
2136
|
-
fontSize: fontSize,
|
|
2137
|
-
fillColor: new paper.Color(cellColor),
|
|
2138
|
-
justification: col.align || 'center'
|
|
2139
|
-
})
|
|
2140
|
-
elements.push({ type: 'text', id: cellText.id })
|
|
2141
|
-
cellX += colWidth
|
|
2142
|
-
})
|
|
2143
|
-
})
|
|
2144
|
-
|
|
2145
|
-
return { success: true, elements, width, height: totalHeight, type: 'table' }
|
|
2146
|
-
}
|