foliko 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
- package/.agent/plugins/poster-plugin/package.json +2 -1
- package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
- package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
- package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
- package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
- package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
- package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
- package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
- package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
- package/.agent/plugins/poster-plugin/src/composer.js +392 -150
- package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
- package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
- package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
- package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
- package/.agent/plugins/poster-plugin/src/index.js +430 -7
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
- package/.agent/plugins/poster-plugin/test-background.svg +1 -0
- package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
- package/.agent/plugins/poster-plugin/test-image.png +0 -0
- package/.agent/sessions/cli_default.json +1089 -145
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
- package/.claude/settings.local.json +6 -1
- package/output/beef-love-poster.png +0 -0
- package/output/international-news-daily.png +0 -0
- package/package.json +2 -1
- package/plugins/extension-executor-plugin.js +33 -32
- package/plugins/file-system-plugin.js +4 -19
- package/plugins/subagent-plugin.js +37 -14
- package/plugins/weixin-plugin.js +167 -47
- package/poster-test-2.png +0 -0
- package/skills/poster-guide/SKILL.md +497 -5
- package/src/core/agent-chat.js +141 -8
- package/src/core/agent.js +6 -3
- package/calc_tokens_weixin.js +0 -81
- package/foliko-creative-3.png +0 -0
- package/foliko-creative-4.png +0 -0
- package/foliko-creative-5.png +0 -0
- package/story-cover-book-v2.png +0 -0
- package/story-cover-japanese-1.png +0 -0
- package/story-cover-japanese-2.png +0 -0
- package/story-cover-japanese-3.png +0 -0
- package/story-cover-moran.png +0 -0
- package/undefined.png +0 -0
|
@@ -3,12 +3,44 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const paper = require('paper')
|
|
6
|
+
const { loadImageAsRaster } = require('../utils/imageLoader')
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* 添加背景(支持纯色、渐变或图片)
|
|
9
10
|
*/
|
|
10
|
-
function addBackground(project, canvas, args) {
|
|
11
|
-
const { color, gradient } = args
|
|
11
|
+
async function addBackground(project, canvas, args) {
|
|
12
|
+
const { color, gradient, image } = args
|
|
13
|
+
|
|
14
|
+
if (image) {
|
|
15
|
+
const { raster } = await loadImageAsRaster(project, image, { x: 0, y: 0 })
|
|
16
|
+
|
|
17
|
+
raster.onLoad = () => {
|
|
18
|
+
// 计算缩放比例,使图片覆盖整个画布(cover 模式)
|
|
19
|
+
const canvasRatio = canvas.width / canvas.height
|
|
20
|
+
const imageRatio = raster.width / raster.height
|
|
21
|
+
|
|
22
|
+
let scaledWidth, scaledHeight, offsetX, offsetY
|
|
23
|
+
|
|
24
|
+
if (imageRatio > canvasRatio) {
|
|
25
|
+
// 图片更宽,以高度为基准缩放
|
|
26
|
+
scaledHeight = canvas.height
|
|
27
|
+
scaledWidth = raster.width * (canvas.height / raster.height)
|
|
28
|
+
offsetX = (canvas.width - scaledWidth) / 2
|
|
29
|
+
offsetY = 0
|
|
30
|
+
} else {
|
|
31
|
+
// 图片更高,以宽度为基准缩放
|
|
32
|
+
scaledWidth = canvas.width
|
|
33
|
+
scaledHeight = raster.height * (canvas.width / raster.width)
|
|
34
|
+
offsetX = 0
|
|
35
|
+
offsetY = (canvas.height - scaledHeight) / 2
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
raster.bounds = new paper.Rectangle(offsetX, offsetY, scaledWidth, scaledHeight)
|
|
39
|
+
raster.sendToBack()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { success: true, message: 'Background image added' }
|
|
43
|
+
}
|
|
12
44
|
|
|
13
45
|
if (gradient) {
|
|
14
46
|
const { type, colors, direction } = gradient
|
|
@@ -43,7 +75,7 @@ function addBackground(project, canvas, args) {
|
|
|
43
75
|
} else if (color) {
|
|
44
76
|
project.activeLayer.fillColor = new paper.Color(color)
|
|
45
77
|
} else {
|
|
46
|
-
throw new Error('Must provide color or
|
|
78
|
+
throw new Error('Must provide color, gradient, or image')
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
return { success: true, message: 'Background added' }
|
|
@@ -3,65 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const paper = require('paper')
|
|
6
|
-
const
|
|
7
|
-
const path = require('path')
|
|
6
|
+
const { loadImageAsRaster } = require('../utils/imageLoader')
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* 添加图片(支持本地路径、URL、Base64)
|
|
11
10
|
*/
|
|
12
|
-
function addImage(project, args) {
|
|
11
|
+
async function addImage(project, args) {
|
|
13
12
|
const { src, x, y, width, height, opacity } = args
|
|
14
13
|
|
|
15
14
|
try {
|
|
16
|
-
|
|
17
|
-
let absolutePath = src
|
|
18
|
-
if (!path.isAbsolute(absolutePath)) {
|
|
19
|
-
absolutePath = path.join(process.cwd(), absolutePath)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 检查文件是否存在
|
|
23
|
-
if (!fs.existsSync(absolutePath)) {
|
|
24
|
-
return { success: false, error: `文件不存在: ${absolutePath}` }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 读取文件并转为 Base64
|
|
28
|
-
const buffer = fs.readFileSync(absolutePath)
|
|
29
|
-
const ext = path.extname(absolutePath).toLowerCase()
|
|
30
|
-
const mimeTypes = {
|
|
31
|
-
'.png': 'image/png',
|
|
32
|
-
'.jpg': 'image/jpeg',
|
|
33
|
-
'.jpeg': 'image/jpeg',
|
|
34
|
-
'.gif': 'image/gif',
|
|
35
|
-
'.webp': 'image/webp',
|
|
36
|
-
'.bmp': 'image/bmp'
|
|
37
|
-
}
|
|
38
|
-
const mimeType = mimeTypes[ext] || 'image/png'
|
|
39
|
-
const imageUrl = `data:${mimeType};base64,${buffer.toString('base64')}`
|
|
40
|
-
|
|
41
|
-
const raster = new paper.Raster(imageUrl)
|
|
42
|
-
|
|
43
|
-
// 等待图片加载完成
|
|
44
|
-
raster.onLoad = () => {
|
|
45
|
-
if (width && height) {
|
|
46
|
-
raster.bounds = new paper.Rectangle(x, y, width, height)
|
|
47
|
-
} else if (width) {
|
|
48
|
-
const scale = width / raster.width
|
|
49
|
-
raster.bounds = new paper.Rectangle(x, y, width, raster.height * scale)
|
|
50
|
-
} else if (height) {
|
|
51
|
-
const scale = height / raster.height
|
|
52
|
-
raster.bounds = new paper.Rectangle(x, y, raster.width * scale, height)
|
|
53
|
-
} else {
|
|
54
|
-
raster.position = new paper.Point(x, y)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (opacity !== undefined) raster.opacity = opacity
|
|
58
|
-
}
|
|
15
|
+
const { raster } = await loadImageAsRaster(project, src, { x, y, width, height }, opacity)
|
|
59
16
|
|
|
60
17
|
return {
|
|
61
18
|
success: true,
|
|
62
19
|
id: raster.id,
|
|
63
20
|
type: 'image',
|
|
64
|
-
path:
|
|
21
|
+
path: src,
|
|
65
22
|
}
|
|
66
23
|
} catch (err) {
|
|
67
24
|
return { success: false, error: `Failed to load image: ${err.message}` }
|
|
@@ -9,6 +9,7 @@ const addPolygon = require('./polygon')
|
|
|
9
9
|
const addImage = require('./image')
|
|
10
10
|
const addText = require('./text')
|
|
11
11
|
const addArtText = require('./artText')
|
|
12
|
+
const addRichText = require('./richText')
|
|
12
13
|
const addBackground = require('./background')
|
|
13
14
|
const { addSVG, exportSVG } = require('./svg')
|
|
14
15
|
|
|
@@ -20,6 +21,7 @@ module.exports = {
|
|
|
20
21
|
addImage,
|
|
21
22
|
addText,
|
|
22
23
|
addArtText,
|
|
24
|
+
addRichText,
|
|
23
25
|
addBackground,
|
|
24
26
|
addSVG,
|
|
25
27
|
exportSVG,
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 富文本组件 - 支持旋转和多种文本样式
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const { validateFont, getDefaultFont, getRegisteredFonts } = require('../fonts')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检测文本是否包含 emoji
|
|
10
|
+
*/
|
|
11
|
+
function containsEmoji(text) {
|
|
12
|
+
if (!text || typeof text !== 'string') return false
|
|
13
|
+
const emojiRanges = [
|
|
14
|
+
/\u{1F600}-\u{1F64F}/u, /\u{1F300}-\u{1F5FF}/u, /\u{1F680}-\u{1F6FF}/u,
|
|
15
|
+
/\u{1F700}-\u{1F77F}/u, /\u{1F780}-\u{1F7FF}/u, /\u{1F800}-\u{1F8FF}/u,
|
|
16
|
+
/\u{1F900}-\u{1F9FF}/u, /\u{1FA00}-\u{1FA6F}/u, /\u{1FA70}-\u{1FAFF}/u,
|
|
17
|
+
/\u{2600}-\u{26FF}/u, /\u{2700}-\u{27BF}/u,
|
|
18
|
+
]
|
|
19
|
+
for (const range of emojiRanges) {
|
|
20
|
+
if (range.test(text)) return true
|
|
21
|
+
}
|
|
22
|
+
return /[\u{1F300}-\u{1F9FF}]/u.test(text)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 获取适合的字体(考虑 emoji)
|
|
27
|
+
*/
|
|
28
|
+
function getFontForText(requestedFont, text) {
|
|
29
|
+
const baseFont = validateFont(requestedFont) || getDefaultFont()
|
|
30
|
+
if (containsEmoji(text)) {
|
|
31
|
+
const registeredFonts = getRegisteredFonts()
|
|
32
|
+
const emojiFont = registeredFonts.find(f => {
|
|
33
|
+
if (!f) return false
|
|
34
|
+
const lower = f.toLowerCase()
|
|
35
|
+
return lower.includes('color emoji') || lower.includes('noto emoji') ||
|
|
36
|
+
lower.includes('segoe ui emoji') || lower.includes('symbola') || lower.includes('emoji')
|
|
37
|
+
})
|
|
38
|
+
if (emojiFont) return emojiFont
|
|
39
|
+
}
|
|
40
|
+
return baseFont
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 添加富文本
|
|
45
|
+
* @param {paper.Project} project - Paper.js 项目
|
|
46
|
+
* @param {Object} args - 参数
|
|
47
|
+
* @returns {Object} 结果
|
|
48
|
+
*/
|
|
49
|
+
function addRichText(project, args) {
|
|
50
|
+
const {
|
|
51
|
+
// 位置和尺寸
|
|
52
|
+
x = 0,
|
|
53
|
+
y = 0,
|
|
54
|
+
width, // 文本区域宽度(用于自动换行)
|
|
55
|
+
|
|
56
|
+
// 文本内容
|
|
57
|
+
text = '',
|
|
58
|
+
|
|
59
|
+
// 字体样式
|
|
60
|
+
fontSize = 48,
|
|
61
|
+
fontFamily,
|
|
62
|
+
fontWeight, // normal, bold, 100-900
|
|
63
|
+
fontStyle, // normal, italic, oblique
|
|
64
|
+
italic = false,
|
|
65
|
+
bold = false,
|
|
66
|
+
|
|
67
|
+
// 文字装饰
|
|
68
|
+
underline = false,
|
|
69
|
+
strikethrough = false,
|
|
70
|
+
|
|
71
|
+
// 颜色
|
|
72
|
+
color = '#ffffff',
|
|
73
|
+
backgroundColor, // 文字背景色
|
|
74
|
+
gradient, // { colors: ['#fff', '#000'], direction: 0 }
|
|
75
|
+
|
|
76
|
+
// 描边
|
|
77
|
+
strokeColor,
|
|
78
|
+
strokeWidth = 1,
|
|
79
|
+
|
|
80
|
+
// 阴影
|
|
81
|
+
shadow, // { color: '#000', blur: 5, offsetX: 2, offsetY: 2 }
|
|
82
|
+
|
|
83
|
+
// 间距
|
|
84
|
+
letterSpacing = 0,
|
|
85
|
+
lineSpacing = 0, // 行间距增量
|
|
86
|
+
lineHeight, // 行高
|
|
87
|
+
|
|
88
|
+
// 对齐
|
|
89
|
+
align = 'left', // left, center, right, justify
|
|
90
|
+
|
|
91
|
+
// 变换
|
|
92
|
+
rotation = 0, // 旋转角度(度)
|
|
93
|
+
scale,
|
|
94
|
+
|
|
95
|
+
// 透明度
|
|
96
|
+
opacity = 1,
|
|
97
|
+
|
|
98
|
+
// 换行
|
|
99
|
+
wrap = true, // 是否自动换行
|
|
100
|
+
} = args
|
|
101
|
+
|
|
102
|
+
const font = getFontForText(fontFamily, text)
|
|
103
|
+
const effectiveFontWeight = bold ? 'bold' : (fontWeight || 'normal')
|
|
104
|
+
const effectiveFontStyle = italic ? 'italic' : (fontStyle || 'normal')
|
|
105
|
+
|
|
106
|
+
// 创建文本项
|
|
107
|
+
const textItem = new paper.PointText({
|
|
108
|
+
point: [x, y + fontSize],
|
|
109
|
+
content: text,
|
|
110
|
+
fontSize,
|
|
111
|
+
fontFamily: font,
|
|
112
|
+
fontWeight: effectiveFontWeight,
|
|
113
|
+
fontStyle: effectiveFontStyle,
|
|
114
|
+
fillColor: new paper.Color(color),
|
|
115
|
+
justification: align,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// 字母间距
|
|
119
|
+
if (letterSpacing !== 0) {
|
|
120
|
+
textItem.letterSpacing = letterSpacing
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 行高
|
|
124
|
+
if (lineHeight) {
|
|
125
|
+
textItem.leading = lineHeight
|
|
126
|
+
} else if (lineSpacing !== 0) {
|
|
127
|
+
textItem.leading = fontSize + lineSpacing
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 下划线和删除线 - 使用字符样式
|
|
131
|
+
if (underline || strikethrough) {
|
|
132
|
+
textItem.decorations = []
|
|
133
|
+
if (underline) textItem.decorations.push('underline')
|
|
134
|
+
if (strikethrough) textItem.decorations.push('strikethrough')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 渐变填充
|
|
138
|
+
if (gradient && gradient.colors && gradient.colors.length > 0) {
|
|
139
|
+
const colors = gradient.colors.map(c => new paper.Color(c))
|
|
140
|
+
const bounds = textItem.bounds
|
|
141
|
+
if (gradient.direction !== undefined) {
|
|
142
|
+
// 自定义方向
|
|
143
|
+
const angle = gradient.direction * Math.PI / 180
|
|
144
|
+
const diagonal = Math.sqrt(bounds.width ** 2 + bounds.height ** 2)
|
|
145
|
+
textItem.fillColor = new paper.Color({
|
|
146
|
+
gradient: { stops: colors },
|
|
147
|
+
origin: new paper.Point(
|
|
148
|
+
bounds.center.x - Math.cos(angle) * diagonal / 2,
|
|
149
|
+
bounds.center.y - Math.sin(angle) * diagonal / 2
|
|
150
|
+
),
|
|
151
|
+
destination: new paper.Point(
|
|
152
|
+
bounds.center.x + Math.cos(angle) * diagonal / 2,
|
|
153
|
+
bounds.center.y + Math.sin(angle) * diagonal / 2
|
|
154
|
+
),
|
|
155
|
+
})
|
|
156
|
+
} else {
|
|
157
|
+
// 水平渐变
|
|
158
|
+
textItem.fillColor = new paper.Color({
|
|
159
|
+
gradient: { stops: colors },
|
|
160
|
+
origin: bounds.topLeft,
|
|
161
|
+
destination: bounds.topRight,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 背景色 - 创建背景矩形
|
|
167
|
+
let bgRect = null
|
|
168
|
+
if (backgroundColor) {
|
|
169
|
+
const padding = 10
|
|
170
|
+
bgRect = new paper.Path.Rectangle({
|
|
171
|
+
point: [textItem.bounds.left - padding, textItem.bounds.top - padding],
|
|
172
|
+
size: [textItem.bounds.width + padding * 2, textItem.bounds.height + padding * 2],
|
|
173
|
+
fillColor: new paper.Color(backgroundColor),
|
|
174
|
+
radius: 4,
|
|
175
|
+
})
|
|
176
|
+
bgRect.sendToBack()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 描边
|
|
180
|
+
if (strokeColor) {
|
|
181
|
+
textItem.strokeColor = new paper.Color(strokeColor)
|
|
182
|
+
textItem.strokeWidth = strokeWidth
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 阴影
|
|
186
|
+
if (shadow) {
|
|
187
|
+
textItem.shadowColor = new paper.Color(shadow.color || '#000000')
|
|
188
|
+
textItem.shadowBlur = shadow.blur || 5
|
|
189
|
+
textItem.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 透明度
|
|
193
|
+
if (opacity !== 1) {
|
|
194
|
+
textItem.opacity = opacity
|
|
195
|
+
if (bgRect) bgRect.opacity = opacity
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 旋转
|
|
199
|
+
if (rotation !== 0) {
|
|
200
|
+
textItem.rotate(rotation, new paper.Point(x, y + fontSize))
|
|
201
|
+
if (bgRect) bgRect.rotate(rotation, bgRect.bounds.center)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 缩放
|
|
205
|
+
if (scale !== undefined) {
|
|
206
|
+
if (typeof scale === 'number') {
|
|
207
|
+
textItem.scale(scale, scale, new paper.Point(x, y + fontSize))
|
|
208
|
+
if (bgRect) bgRect.scale(scale, scale, bgRect.bounds.center)
|
|
209
|
+
} else if (typeof scale === 'object' && (scale.x !== undefined || scale.y !== undefined)) {
|
|
210
|
+
const sx = scale.x || 1
|
|
211
|
+
const sy = scale.y || sx
|
|
212
|
+
textItem.scale(sx, sy, new paper.Point(x, y + fontSize))
|
|
213
|
+
if (bgRect) bgRect.scale(sx, sy, bgRect.bounds.center)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
success: true,
|
|
219
|
+
id: textItem.id,
|
|
220
|
+
type: 'richText',
|
|
221
|
+
bounds: {
|
|
222
|
+
x: textItem.bounds.x,
|
|
223
|
+
y: textItem.bounds.y,
|
|
224
|
+
width: textItem.bounds.width,
|
|
225
|
+
height: textItem.bounds.height,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = addRichText
|
|
@@ -4,51 +4,67 @@
|
|
|
4
4
|
|
|
5
5
|
const paper = require('paper')
|
|
6
6
|
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* 添加 SVG
|
|
10
|
-
*
|
|
11
|
-
* @param {Object} project - Paper.js 项目
|
|
12
|
-
* @param {Object} args - 组件参数
|
|
13
|
-
* @param {string} args.src - SVG 文件路径或 SVG 字符串
|
|
14
|
-
* @param {number} args.x - X坐标
|
|
15
|
-
* @param {number} args.y - Y坐标
|
|
16
|
-
* @param {number} args.width - 宽度
|
|
17
|
-
* @param {number} args.height - 高度
|
|
18
|
-
* @param {number} args.opacity - 透明度 0-1
|
|
19
11
|
*/
|
|
20
|
-
function addSVG(project, args) {
|
|
21
|
-
const { src, x, y, width, height, opacity } = args
|
|
12
|
+
async function addSVG(project, args) {
|
|
13
|
+
const { src, x = 0, y = 0, width, height, opacity = 1 } = args
|
|
14
|
+
|
|
15
|
+
// 确保 src 是字符串
|
|
16
|
+
if (typeof src !== 'string') {
|
|
17
|
+
return { success: false, error: 'SVG source must be a string' }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!src) {
|
|
21
|
+
return { success: false, error: 'SVG source is required' }
|
|
22
|
+
}
|
|
22
23
|
|
|
23
24
|
let svgContent = src
|
|
24
25
|
|
|
25
26
|
// 如果是文件路径,读取文件内容
|
|
26
27
|
if (!src.startsWith('<') && !src.startsWith('<?xml')) {
|
|
27
28
|
try {
|
|
28
|
-
|
|
29
|
+
let filePath = src
|
|
30
|
+
if (!path.isAbsolute(filePath)) {
|
|
31
|
+
filePath = path.join(process.cwd(), filePath)
|
|
32
|
+
}
|
|
33
|
+
svgContent = fs.readFileSync(filePath, 'utf8')
|
|
29
34
|
} catch (e) {
|
|
30
35
|
return { success: false, error: `Failed to read SVG file: ${e.message}` }
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
// 导入 SVG
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
// 导入 SVG 到指定项目
|
|
40
|
+
let svg
|
|
41
|
+
try {
|
|
42
|
+
svg = project.importSVG(svgContent)
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { success: false, error: `Failed to import SVG: ${e.message}` }
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
if (!svg) {
|
|
38
48
|
return { success: false, error: 'Failed to import SVG' }
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
// 确保 SVG 添加到活动层
|
|
52
|
+
if (project && project.activeLayer && svg.parent !== project.activeLayer) {
|
|
53
|
+
project.activeLayer.addChild(svg)
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
// 设置位置
|
|
42
57
|
svg.position = new paper.Point(x, y)
|
|
43
58
|
|
|
44
59
|
// 设置尺寸
|
|
45
60
|
if (width && height) {
|
|
46
|
-
svg.bounds.width
|
|
47
|
-
svg.bounds.height
|
|
61
|
+
const scaleX = width / svg.bounds.width
|
|
62
|
+
const scaleY = height / svg.bounds.height
|
|
63
|
+
svg.scale(Math.min(scaleX, scaleY), svg.bounds.center)
|
|
48
64
|
} else if (width) {
|
|
49
|
-
svg.scale(width / svg.bounds.width)
|
|
65
|
+
svg.scale(width / svg.bounds.width, svg.bounds.center)
|
|
50
66
|
} else if (height) {
|
|
51
|
-
svg.scale(height / svg.bounds.height)
|
|
67
|
+
svg.scale(height / svg.bounds.height, svg.bounds.center)
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
// 设置透明度
|