foliko 1.0.87 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/data/default.json +3 -108
- package/.agent/data/plugins-state.json +34 -1
- package/.agent/mcp_config.json +0 -1
- package/.agent/memory/core.md +1 -0
- package/.agent/memory/project/mnn93ogy-ypjn27.md +9 -0
- package/.agent/memory/project/mnn98fqy-5nhc1u.md +25 -0
- package/.agent/memory/user/mnm67t9m-x8rekk.md +9 -0
- package/.agent/memory/user/mnn5mmqh-w6aktx.md +11 -0
- package/.agent/memory/user/mnnbfhhn-dk1bd1.md +22 -0
- package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
- package/.agent/plugins/poster-plugin/README.md +304 -0
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.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 +13 -0
- package/.agent/plugins/poster-plugin/package.json +28 -0
- package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
- package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
- package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
- package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
- package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
- package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
- package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
- package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
- package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
- package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
- package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
- package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
- package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
- package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
- package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
- package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
- package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
- package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
- package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
- package/.agent/plugins/poster-plugin/src/elements/text.js +38 -0
- package/.agent/plugins/poster-plugin/src/fonts.js +118 -0
- package/.agent/plugins/poster-plugin/src/index.js +1659 -0
- package/.agent/plugins/poster-plugin/src/presets.js +36 -0
- package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
- package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
- package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
- package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
- package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
- package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
- package/.agent/sessions/cli_default.json +24265 -0
- package/.agent/weixin.json +6 -0
- package/.claude/settings.local.json +5 -8
- package/CLAUDE.md +144 -108
- package/docs/CONTEXT_DESIGN.md +1596 -0
- package/examples/test-concurrent-chat.js +60 -60
- package/output/beef-love-poster.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +2 -1
- package/plugins/extension-executor-plugin.js +11 -0
- package/plugins/memory-plugin.js +984 -0
- package/plugins/session-plugin.js +57 -1
- package/plugins/weixin-plugin.js +24 -22
- package/skills/poster-guide/SKILL.md +743 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/skills/skill-guide/SKILL.md +130 -108
- package/src/capabilities/skill-manager.js +99 -0
- package/src/core/agent-chat.js +538 -138
- package/src/core/agent-context.js +188 -0
- package/src/core/agent.js +6 -2
- package/src/core/context-manager.js +283 -0
- package/src/core/framework.js +264 -3
- package/src/core/plugin-manager.js +79 -2
- package/src/core/request-context.js +98 -0
- package/src/core/session-context.js +341 -0
- package/src/core/session-storage.js +274 -0
- package/src/executors/mcp-executor.js +2 -2
- package/src/utils/index.js +239 -67
- package/src/utils/plugin-helpers.js +17 -0
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/temp-repo/LICENSE +0 -201
- package/.agent/plugins/temp-repo/puppeteer-plugin/README.md +0 -147
- package/.agent/plugins/temp-repo/puppeteer-plugin/index.js +0 -1418
- package/.agent/plugins/temp-repo/puppeteer-plugin/package.json +0 -9
- package/.agent/plugins/test_plugin.py +0 -304
- package/examples/test-chat-debug.js +0 -102
- package/examples/test-chat-result.js +0 -76
- package/examples/test-chat-stream-diff.js +0 -63
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 艺术文字元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const { validateFont, getDefaultFont } = require('../fonts')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 添加艺术文字
|
|
10
|
+
*/
|
|
11
|
+
function addArtText(project, args) {
|
|
12
|
+
const {
|
|
13
|
+
text,
|
|
14
|
+
x, y,
|
|
15
|
+
fontSize,
|
|
16
|
+
fontFamily,
|
|
17
|
+
gradient,
|
|
18
|
+
strokeColor,
|
|
19
|
+
strokeWidth,
|
|
20
|
+
shadow,
|
|
21
|
+
} = args
|
|
22
|
+
|
|
23
|
+
const textItem = new paper.PointText({
|
|
24
|
+
point: [x, y],
|
|
25
|
+
content: text,
|
|
26
|
+
fontSize: fontSize || 120,
|
|
27
|
+
fontFamily: validateFont(fontFamily) || getDefaultFont(),
|
|
28
|
+
fillColor: gradient
|
|
29
|
+
? new paper.Color(gradient.colors[0])
|
|
30
|
+
: new paper.Color('#ffffff'),
|
|
31
|
+
justification: 'center',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// 应用渐变
|
|
35
|
+
if (gradient && gradient.colors.length > 0) {
|
|
36
|
+
const colors = gradient.colors.map(c => new paper.Color(c))
|
|
37
|
+
textItem.fillColor = new paper.Color({
|
|
38
|
+
gradient: { stops: colors },
|
|
39
|
+
origin: textItem.bounds.topLeft,
|
|
40
|
+
destination: textItem.bounds.topRight,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 应用描边
|
|
45
|
+
if (strokeColor) {
|
|
46
|
+
textItem.strokeColor = new paper.Color(strokeColor)
|
|
47
|
+
textItem.strokeWidth = strokeWidth || 2
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 应用阴影
|
|
51
|
+
if (shadow) {
|
|
52
|
+
textItem.shadowColor = new paper.Color(shadow.color)
|
|
53
|
+
textItem.shadowBlur = shadow.blur || 10
|
|
54
|
+
textItem.shadowOffset = new paper.Point(shadow.offsetX || 3, shadow.offsetY || 3)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { success: true, id: textItem.id, type: 'artText' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = addArtText
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 背景元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 添加背景
|
|
9
|
+
*/
|
|
10
|
+
function addBackground(project, canvas, args) {
|
|
11
|
+
const { color, gradient } = args
|
|
12
|
+
|
|
13
|
+
if (gradient) {
|
|
14
|
+
const { type, colors, direction } = gradient
|
|
15
|
+
const paperColors = colors.map(c => new paper.Color(c))
|
|
16
|
+
|
|
17
|
+
if (type === 'linear') {
|
|
18
|
+
const angle = (direction || 45) * Math.PI / 180
|
|
19
|
+
const diagonal = Math.sqrt(canvas.width ** 2 + canvas.height ** 2)
|
|
20
|
+
const start = new paper.Point(
|
|
21
|
+
canvas.width / 2 - Math.cos(angle) * diagonal / 2,
|
|
22
|
+
canvas.height / 2 - Math.sin(angle) * diagonal / 2
|
|
23
|
+
)
|
|
24
|
+
const stop = new paper.Point(
|
|
25
|
+
canvas.width / 2 + Math.cos(angle) * diagonal / 2,
|
|
26
|
+
canvas.height / 2 + Math.sin(angle) * diagonal / 2
|
|
27
|
+
)
|
|
28
|
+
project.activeLayer.fillColor = new paper.Color({
|
|
29
|
+
gradient: { stops: paperColors },
|
|
30
|
+
origin: start,
|
|
31
|
+
destination: stop,
|
|
32
|
+
})
|
|
33
|
+
} else {
|
|
34
|
+
// radial
|
|
35
|
+
const center = new paper.Point(canvas.width / 2, canvas.height / 2)
|
|
36
|
+
const radius = Math.max(canvas.width, canvas.height) / 2
|
|
37
|
+
project.activeLayer.fillColor = new paper.Color({
|
|
38
|
+
gradient: { stops: paperColors },
|
|
39
|
+
origin: center,
|
|
40
|
+
destination: center.add(new paper.Point(radius, 0)),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
} else if (color) {
|
|
44
|
+
project.activeLayer.fillColor = new paper.Color(color)
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error('Must provide color or gradient')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { success: true, message: 'Background added' }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = addBackground
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 圆形/椭圆元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 添加圆形或椭圆
|
|
9
|
+
*/
|
|
10
|
+
function addCircle(project, args) {
|
|
11
|
+
const {
|
|
12
|
+
cx, cy, rx, ry,
|
|
13
|
+
fill, stroke, strokeWidth, opacity,
|
|
14
|
+
} = args
|
|
15
|
+
|
|
16
|
+
const circle = new paper.Path.Ellipse({
|
|
17
|
+
center: [cx, cy],
|
|
18
|
+
radius: [rx, ry || rx],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (fill) circle.fillColor = new paper.Color(fill)
|
|
22
|
+
if (stroke) {
|
|
23
|
+
circle.strokeColor = new paper.Color(stroke)
|
|
24
|
+
circle.strokeWidth = strokeWidth || 1
|
|
25
|
+
}
|
|
26
|
+
if (opacity !== undefined) circle.opacity = opacity
|
|
27
|
+
|
|
28
|
+
return { success: true, id: circle.id, type: 'circle' }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = addCircle
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 添加图片(支持本地路径、URL、Base64)
|
|
11
|
+
*/
|
|
12
|
+
function addImage(project, args) {
|
|
13
|
+
const { src, x, y, width, height, opacity } = args
|
|
14
|
+
|
|
15
|
+
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
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
id: raster.id,
|
|
63
|
+
type: 'image',
|
|
64
|
+
path: absolutePath,
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return { success: false, error: `Failed to load image: ${err.message}` }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = addImage
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基础元素模块导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const addRectangle = require('./rectangle')
|
|
6
|
+
const addCircle = require('./circle')
|
|
7
|
+
const addLine = require('./line')
|
|
8
|
+
const addPolygon = require('./polygon')
|
|
9
|
+
const addImage = require('./image')
|
|
10
|
+
const addText = require('./text')
|
|
11
|
+
const addArtText = require('./artText')
|
|
12
|
+
const addBackground = require('./background')
|
|
13
|
+
const { addSVG, exportSVG } = require('./svg')
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
addRectangle,
|
|
17
|
+
addCircle,
|
|
18
|
+
addLine,
|
|
19
|
+
addPolygon,
|
|
20
|
+
addImage,
|
|
21
|
+
addText,
|
|
22
|
+
addArtText,
|
|
23
|
+
addBackground,
|
|
24
|
+
addSVG,
|
|
25
|
+
exportSVG,
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 线条元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 添加线条
|
|
9
|
+
*/
|
|
10
|
+
function addLine(project, args) {
|
|
11
|
+
const { x1, y1, x2, y2, stroke, strokeWidth } = args
|
|
12
|
+
|
|
13
|
+
const line = new paper.Path.Line({
|
|
14
|
+
from: [x1, y1],
|
|
15
|
+
to: [x2, y2],
|
|
16
|
+
strokeColor: new paper.Color(stroke || '#ffffff'),
|
|
17
|
+
strokeWidth: strokeWidth || 2,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
return { success: true, id: line.id, type: 'line' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = addLine
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多边形元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 添加多边形
|
|
9
|
+
*/
|
|
10
|
+
function addPolygon(project, args) {
|
|
11
|
+
const {
|
|
12
|
+
cx, cy, radius, sides,
|
|
13
|
+
fill, stroke, strokeWidth, opacity,
|
|
14
|
+
} = args
|
|
15
|
+
|
|
16
|
+
const polygon = new paper.Path.RegularPolygon({
|
|
17
|
+
center: [cx, cy],
|
|
18
|
+
radius: radius,
|
|
19
|
+
sides: sides,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
if (fill) polygon.fillColor = new paper.Color(fill)
|
|
23
|
+
if (stroke) {
|
|
24
|
+
polygon.strokeColor = new paper.Color(stroke)
|
|
25
|
+
polygon.strokeWidth = strokeWidth || 1
|
|
26
|
+
}
|
|
27
|
+
if (opacity !== undefined) polygon.opacity = opacity
|
|
28
|
+
|
|
29
|
+
return { success: true, id: polygon.id, type: 'polygon' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = addPolygon
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 矩形元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 添加矩形
|
|
9
|
+
*/
|
|
10
|
+
function addRectangle(project, args) {
|
|
11
|
+
const {
|
|
12
|
+
x, y, width, height,
|
|
13
|
+
fill, stroke, strokeWidth, radius, opacity,
|
|
14
|
+
} = args
|
|
15
|
+
|
|
16
|
+
const rect = new paper.Path.Rectangle({
|
|
17
|
+
point: [x, y],
|
|
18
|
+
size: [width, height],
|
|
19
|
+
radius: radius || 0,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
if (fill) rect.fillColor = new paper.Color(fill)
|
|
23
|
+
if (stroke) {
|
|
24
|
+
rect.strokeColor = new paper.Color(stroke)
|
|
25
|
+
rect.strokeWidth = strokeWidth || 1
|
|
26
|
+
}
|
|
27
|
+
if (opacity !== undefined) rect.opacity = opacity
|
|
28
|
+
|
|
29
|
+
return { success: true, id: rect.id, type: 'rectangle' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = addRectangle
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG 元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 添加 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
|
+
*/
|
|
20
|
+
function addSVG(project, args) {
|
|
21
|
+
const { src, x, y, width, height, opacity } = args
|
|
22
|
+
|
|
23
|
+
let svgContent = src
|
|
24
|
+
|
|
25
|
+
// 如果是文件路径,读取文件内容
|
|
26
|
+
if (!src.startsWith('<') && !src.startsWith('<?xml')) {
|
|
27
|
+
try {
|
|
28
|
+
svgContent = fs.readFileSync(src, 'utf8')
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return { success: false, error: `Failed to read SVG file: ${e.message}` }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 导入 SVG
|
|
35
|
+
const svg = paper.project.importSVG(svgContent)
|
|
36
|
+
|
|
37
|
+
if (!svg) {
|
|
38
|
+
return { success: false, error: 'Failed to import SVG' }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 设置位置
|
|
42
|
+
svg.position = new paper.Point(x, y)
|
|
43
|
+
|
|
44
|
+
// 设置尺寸
|
|
45
|
+
if (width && height) {
|
|
46
|
+
svg.bounds.width = width
|
|
47
|
+
svg.bounds.height = height
|
|
48
|
+
} else if (width) {
|
|
49
|
+
svg.scale(width / svg.bounds.width)
|
|
50
|
+
} else if (height) {
|
|
51
|
+
svg.scale(height / svg.bounds.height)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 设置透明度
|
|
55
|
+
if (opacity !== undefined) {
|
|
56
|
+
svg.opacity = opacity
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
id: svg.id,
|
|
62
|
+
type: 'svg',
|
|
63
|
+
width: svg.bounds.width,
|
|
64
|
+
height: svg.bounds.height,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 创建 SVG 导出
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} project - Paper.js 项目
|
|
72
|
+
* @param {string} filename - 文件名
|
|
73
|
+
* @param {string} outputDir - 输出目录
|
|
74
|
+
*/
|
|
75
|
+
function exportSVG(project, filename, outputDir = '.') {
|
|
76
|
+
const svg = project.exportSVG({
|
|
77
|
+
asString: true,
|
|
78
|
+
bounds: 'content',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const filepath = require('path').join(outputDir, `${filename}.svg`)
|
|
82
|
+
fs.writeFileSync(filepath, svg)
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
filepath,
|
|
87
|
+
filename: `${filename}.svg`,
|
|
88
|
+
size: Buffer.byteLength(svg, 'utf8'),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { addSVG, exportSVG }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文字元素
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const paper = require('paper')
|
|
6
|
+
const { validateFont, getDefaultFont } = require('../fonts')
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 添加文字
|
|
10
|
+
*/
|
|
11
|
+
function addText(project, args) {
|
|
12
|
+
const {
|
|
13
|
+
text,
|
|
14
|
+
x, y,
|
|
15
|
+
fontSize, fontFamily, color,
|
|
16
|
+
align,
|
|
17
|
+
shadow,
|
|
18
|
+
} = args
|
|
19
|
+
|
|
20
|
+
const textItem = new paper.PointText({
|
|
21
|
+
point: [x, y],
|
|
22
|
+
content: text,
|
|
23
|
+
fontSize: fontSize || 48,
|
|
24
|
+
fontFamily: validateFont(fontFamily) || getDefaultFont(),
|
|
25
|
+
fillColor: new paper.Color(color || '#ffffff'),
|
|
26
|
+
justification: align || 'left',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
if (shadow) {
|
|
30
|
+
textItem.shadowColor = new paper.Color(shadow.color)
|
|
31
|
+
textItem.shadowBlur = shadow.blur || 5
|
|
32
|
+
textItem.shadowOffset = new paper.Point(shadow.offsetX || 2, shadow.offsetY || 2)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { success: true, id: textItem.id, type: 'text' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = addText
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 字体管理模块
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const { registerFont: registerFontFn } = require('canvas')
|
|
8
|
+
|
|
9
|
+
// 已注册的字体
|
|
10
|
+
const registeredFonts = new Map()
|
|
11
|
+
|
|
12
|
+
// 系统字体路径
|
|
13
|
+
const systemFonts = [
|
|
14
|
+
{ path: 'C:\\Windows\\Fonts\\msyh.ttc', family: 'Microsoft YaHei' },
|
|
15
|
+
{ path: 'C:\\Windows\\Fonts\\msyhbd.ttc', family: 'Microsoft YaHei Bold', weight: 'bold' },
|
|
16
|
+
{ path: 'C:\\Windows\\Fonts\\simhei.ttf', family: 'SimHei' },
|
|
17
|
+
{ path: 'C:\\Windows\\Fonts\\simsun.ttc', family: 'SimSun' },
|
|
18
|
+
{ path: 'C:\\Windows\\Fonts\\Arial.ttf', family: 'Arial' },
|
|
19
|
+
{ path: 'C:\\Windows\\Fonts\\Times New Roman.ttf', family: 'Times New Roman' },
|
|
20
|
+
{ path: 'C:\\Windows\\Fonts\\Consolas.ttf', family: 'Consolas' },
|
|
21
|
+
{ path: 'C:\\Windows\\Fonts\\Georgia.ttf', family: 'Georgia' },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
// 默认字体
|
|
25
|
+
let defaultFontFamily = 'sans-serif'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 注册字体文件
|
|
29
|
+
*/
|
|
30
|
+
function registerFontFile(fontPath, fontFamily, options = {}) {
|
|
31
|
+
if (registeredFonts.has(fontFamily)) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(fontPath)) {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
registerFontFn(fontPath, {
|
|
41
|
+
family: fontFamily,
|
|
42
|
+
weight: options.weight || 'normal',
|
|
43
|
+
style: options.style || 'normal',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
registeredFonts.set(fontFamily, {
|
|
47
|
+
path: fontPath,
|
|
48
|
+
...options,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return true
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.log(`[poster] 注册字体失败: ${fontFamily}`, e.message)
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 初始化字体
|
|
60
|
+
*/
|
|
61
|
+
function initFonts() {
|
|
62
|
+
// 优先尝试注册系统字体
|
|
63
|
+
for (const font of systemFonts) {
|
|
64
|
+
if (registerFontFile(font.path, font.family, { weight: font.weight })) {
|
|
65
|
+
if (font.weight !== 'bold') {
|
|
66
|
+
defaultFontFamily = font.family
|
|
67
|
+
console.log(`[poster] 已注册系统字体: ${font.family}`)
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 确保有默认字体
|
|
74
|
+
if (!registeredFonts.has(defaultFontFamily)) {
|
|
75
|
+
registeredFonts.set('sans-serif', { path: null })
|
|
76
|
+
console.log('[poster] 使用默认字体: sans-serif')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 验证字体是否可用
|
|
82
|
+
*/
|
|
83
|
+
function validateFont(fontFamily) {
|
|
84
|
+
if (!fontFamily) return defaultFontFamily
|
|
85
|
+
if (registeredFonts.has(fontFamily)) return fontFamily
|
|
86
|
+
|
|
87
|
+
const lower = fontFamily.toLowerCase()
|
|
88
|
+
for (const [name] of registeredFonts) {
|
|
89
|
+
if (name.toLowerCase() === lower) return name
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//console.log(`[poster] 字体 "${fontFamily}" 未找到,使用默认字体: ${defaultFontFamily}`)
|
|
93
|
+
return defaultFontFamily
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 获取已注册字体列表
|
|
98
|
+
*/
|
|
99
|
+
function getRegisteredFonts() {
|
|
100
|
+
return Array.from(registeredFonts.keys())
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 获取默认字体
|
|
105
|
+
*/
|
|
106
|
+
function getDefaultFont() {
|
|
107
|
+
return defaultFontFamily
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 初始化
|
|
111
|
+
initFonts()
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
registerFontFile,
|
|
115
|
+
validateFont,
|
|
116
|
+
getRegisteredFonts,
|
|
117
|
+
getDefaultFont,
|
|
118
|
+
}
|