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.
Files changed (57) hide show
  1. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  2. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  3. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  4. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  5. package/.agent/plugins/poster-plugin/package.json +2 -1
  6. package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
  7. package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
  8. package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
  9. package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
  10. package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
  11. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
  12. package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
  13. package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
  14. package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
  15. package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
  16. package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
  17. package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
  18. package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
  19. package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
  20. package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
  21. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
  22. package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
  23. package/.agent/plugins/poster-plugin/src/composer.js +392 -150
  24. package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
  25. package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
  26. package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
  27. package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
  28. package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
  29. package/.agent/plugins/poster-plugin/src/index.js +430 -7
  30. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
  31. package/.agent/plugins/poster-plugin/test-background.svg +1 -0
  32. package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
  33. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  34. package/.agent/sessions/cli_default.json +1089 -145
  35. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
  36. package/.claude/settings.local.json +6 -1
  37. package/output/beef-love-poster.png +0 -0
  38. package/output/international-news-daily.png +0 -0
  39. package/package.json +2 -1
  40. package/plugins/extension-executor-plugin.js +33 -32
  41. package/plugins/file-system-plugin.js +4 -19
  42. package/plugins/subagent-plugin.js +37 -14
  43. package/plugins/weixin-plugin.js +167 -47
  44. package/poster-test-2.png +0 -0
  45. package/skills/poster-guide/SKILL.md +497 -5
  46. package/src/core/agent-chat.js +141 -8
  47. package/src/core/agent.js +6 -3
  48. package/calc_tokens_weixin.js +0 -81
  49. package/foliko-creative-3.png +0 -0
  50. package/foliko-creative-4.png +0 -0
  51. package/foliko-creative-5.png +0 -0
  52. package/story-cover-book-v2.png +0 -0
  53. package/story-cover-japanese-1.png +0 -0
  54. package/story-cover-japanese-2.png +0 -0
  55. package/story-cover-japanese-3.png +0 -0
  56. package/story-cover-moran.png +0 -0
  57. 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 gradient')
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 fs = require('fs')
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: absolutePath,
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
- svgContent = fs.readFileSync(src, 'utf8')
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
- const svg = paper.project.importSVG(svgContent)
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 = width
47
- svg.bounds.height = 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
  // 设置透明度