foliko 1.1.0 → 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 (214) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +412 -3
  39. package/.agent/data/plugins-state.json +173 -174
  40. package/.agent/data/puppeteer-sessions/undefined.json +6 -0
  41. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  42. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  43. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  44. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  45. package/.agent/mcp_config_updated.json +12 -0
  46. package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
  47. package/.agent/plugins/poster-plugin/package.json +2 -1
  48. package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
  49. package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
  50. package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
  51. package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
  52. package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
  53. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
  54. package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
  55. package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
  56. package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
  57. package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
  58. package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
  59. package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
  60. package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
  61. package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
  62. package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
  63. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
  64. package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
  65. package/.agent/plugins/poster-plugin/src/composer.js +392 -150
  66. package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
  67. package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
  68. package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
  69. package/.agent/plugins/poster-plugin/src/elements/polygon.js +37 -6
  70. package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
  71. package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
  72. package/.agent/plugins/poster-plugin/src/elements/text.js +71 -2
  73. package/.agent/plugins/poster-plugin/src/fonts.js +123 -8
  74. package/.agent/plugins/poster-plugin/src/index.js +445 -23
  75. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
  76. package/.agent/plugins/poster-plugin/test-background.svg +1 -0
  77. package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
  78. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  79. package/.agent/plugins/puppeteer-plugin/README.md +147 -0
  80. package/.agent/plugins/puppeteer-plugin/index.js +1418 -0
  81. package/.agent/plugins/puppeteer-plugin/package.json +9 -0
  82. package/.agent/plugins.json +5 -11
  83. package/.agent/rules/GEMINI.md +273 -0
  84. package/.agent/rules/allow-rule.md +77 -0
  85. package/.agent/rules/log-rule.md +83 -0
  86. package/.agent/rules/security-rule.md +93 -0
  87. package/.agent/scripts/auto_preview.py +148 -0
  88. package/.agent/scripts/checklist.py +217 -0
  89. package/.agent/scripts/session_manager.py +120 -0
  90. package/.agent/scripts/verify_all.py +327 -0
  91. package/.agent/sessions/cli_default.json +678 -23580
  92. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +11097 -0
  93. package/.agent/skills/api-patterns/SKILL.md +81 -0
  94. package/.agent/skills/api-patterns/api-style.md +42 -0
  95. package/.agent/skills/api-patterns/auth.md +24 -0
  96. package/.agent/skills/api-patterns/documentation.md +26 -0
  97. package/.agent/skills/api-patterns/graphql.md +41 -0
  98. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  99. package/.agent/skills/api-patterns/response.md +37 -0
  100. package/.agent/skills/api-patterns/rest.md +40 -0
  101. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  102. package/.agent/skills/api-patterns/security-testing.md +122 -0
  103. package/.agent/skills/api-patterns/trpc.md +41 -0
  104. package/.agent/skills/api-patterns/versioning.md +22 -0
  105. package/.agent/skills/app-builder/SKILL.md +75 -0
  106. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  107. package/.agent/skills/app-builder/feature-building.md +53 -0
  108. package/.agent/skills/app-builder/project-detection.md +34 -0
  109. package/.agent/skills/app-builder/scaffolding.md +118 -0
  110. package/.agent/skills/app-builder/tech-stack.md +40 -0
  111. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  112. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  113. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  114. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  115. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  116. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  117. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  118. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  119. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  120. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  121. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  122. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  123. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  124. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  125. package/.agent/skills/architecture/SKILL.md +55 -0
  126. package/.agent/skills/architecture/context-discovery.md +43 -0
  127. package/.agent/skills/architecture/examples.md +94 -0
  128. package/.agent/skills/architecture/pattern-selection.md +68 -0
  129. package/.agent/skills/architecture/patterns-reference.md +50 -0
  130. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  131. package/.agent/skills/clean-code/SKILL.md +201 -0
  132. package/.agent/skills/doc.md +177 -0
  133. package/.agent/skills/frontend-design/SKILL.md +418 -0
  134. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  135. package/.agent/skills/frontend-design/color-system.md +311 -0
  136. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  137. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  138. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  139. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  140. package/.agent/skills/frontend-design/typography-system.md +345 -0
  141. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  142. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  143. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  144. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  145. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  146. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  147. package/.agent/workflows/brainstorm.md +113 -0
  148. package/.agent/workflows/create.md +59 -0
  149. package/.agent/workflows/debug.md +103 -0
  150. package/.agent/workflows/deploy.md +176 -0
  151. package/.agent/workflows/enhance.md +63 -0
  152. package/.agent/workflows/orchestrate.md +237 -0
  153. package/.agent/workflows/plan.md +89 -0
  154. package/.agent/workflows/preview.md +81 -0
  155. package/.agent/workflows/simple-test.md +42 -0
  156. package/.agent/workflows/status.md +86 -0
  157. package/.agent/workflows/structured-orchestrate.md +180 -0
  158. package/.agent/workflows/test.md +144 -0
  159. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  160. package/.claude/settings.local.json +21 -1
  161. package/.env.example +56 -56
  162. package/README.md +441 -441
  163. package/cli/src/commands/chat.js +2 -1
  164. package/output/international-news-daily.png +0 -0
  165. package/package.json +2 -1
  166. package/plugins/extension-executor-plugin.js +91 -12
  167. package/plugins/file-system-plugin.js +4 -19
  168. package/plugins/subagent-plugin.js +37 -14
  169. package/plugins/weixin-plugin.js +168 -40
  170. package/poster-test-2.png +0 -0
  171. package/skills/find-skills/AGENTS.md +162 -162
  172. package/skills/find-skills/SKILL.md +133 -133
  173. package/skills/poster-guide/SKILL.md +1435 -627
  174. package/src/core/agent-chat.js +223 -11
  175. package/src/core/agent.js +6 -3
  176. package/.agent/agents/code-assistant.json +0 -14
  177. package/.agent/agents/email-assistant.json +0 -14
  178. package/.agent/agents/file-assistant.json +0 -15
  179. package/.agent/agents/system-assistant.json +0 -15
  180. package/.agent/agents/web-assistant.json +0 -12
  181. package/.agent/data/ambient/goals.json +0 -50
  182. package/.agent/data/ambient/memories.json +0 -7
  183. package/.agent/data/scheduler/tasks.json +0 -1
  184. package/.agent/memory/core.md +0 -1
  185. package/.agent/memory/project/mnn93ogy-ypjn27.md +0 -9
  186. package/.agent/memory/project/mnn98fqy-5nhc1u.md +0 -25
  187. package/.agent/memory/user/mnm67t9m-x8rekk.md +0 -9
  188. package/.agent/memory/user/mnn5mmqh-w6aktx.md +0 -11
  189. package/.agent/memory/user/mnnbfhhn-dk1bd1.md +0 -22
  190. package/.agent/package.json +0 -8
  191. package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
  192. package/.agent/plugins/daytona/README.md +0 -89
  193. package/.agent/plugins/daytona/index.js +0 -377
  194. package/.agent/plugins/daytona/package.json +0 -12
  195. package/.agent/plugins/marknative/README.md +0 -134
  196. package/.agent/plugins/marknative/index.js +0 -228
  197. package/.agent/plugins/marknative/package.json +0 -12
  198. package/.agent/plugins/marknative/update-readme.js +0 -134
  199. package/.agent/plugins/system-info/index.js +0 -387
  200. package/.agent/plugins/system-info/package.json +0 -4
  201. package/.agent/plugins/system-info/test.js +0 -40
  202. package/.agent/python-scripts/test_sample.py +0 -24
  203. package/.agent/skills/agent-browser/SKILL.md +0 -311
  204. package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
  205. package/.agent/skills/sysinfo/SKILL.md +0 -38
  206. package/.agent/skills/sysinfo/system-info.sh +0 -130
  207. package/.agent/skills/workflow/SKILL.md +0 -324
  208. package/.agent/weixin.json +0 -6
  209. package/.agent/workflows/email-digest.json +0 -50
  210. package/.agent/workflows/file-backup.json +0 -21
  211. package/.agent/workflows/get-ip-notify.json +0 -32
  212. package/.agent/workflows/news-aggregator.json +0 -93
  213. package/.agent/workflows/news-dashboard-v2.json +0 -94
  214. package/.agent/workflows/notification-batch.json +0 -32
@@ -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,
@@ -6,24 +6,55 @@ const paper = require('paper')
6
6
 
7
7
  /**
8
8
  * 添加多边形
9
+ * 支持两种模式:
10
+ * 1. 规则多边形: cx, cy, radius, sides
11
+ * 2. 自定义多边形: points 数组 [[x1,y1], [x2,y2], ...]
9
12
  */
10
13
  function addPolygon(project, args) {
11
14
  const {
12
15
  cx, cy, radius, sides,
16
+ points,
13
17
  fill, stroke, strokeWidth, opacity,
18
+ x = 0, y = 0,
14
19
  } = args
15
20
 
16
- const polygon = new paper.Path.RegularPolygon({
17
- center: [cx, cy],
18
- radius: radius,
19
- sides: sides,
20
- })
21
+ let polygon
21
22
 
22
- if (fill) polygon.fillColor = new paper.Color(fill)
23
+ if (points && Array.isArray(points)) {
24
+ // 使用自定义点创建多边形
25
+ // 每个点应该是 [relativeX, relativeY],相对于 x, y 偏移
26
+ const pathData = points.map((pt, i) => {
27
+ const px = (Array.isArray(pt) ? pt[0] : pt) + x
28
+ const py = (Array.isArray(pt) ? pt[1] : 0) + y
29
+ return i === 0 ? [px, py] : [px, py]
30
+ })
31
+
32
+ polygon = new paper.Path({
33
+ segments: pathData,
34
+ closed: true,
35
+ })
36
+ } else if (cx !== undefined && cy !== undefined) {
37
+ // 使用中心点和半径创建规则多边形
38
+ polygon = new paper.Path.RegularPolygon({
39
+ center: [cx, cy],
40
+ radius: radius || 100,
41
+ sides: sides || 6,
42
+ })
43
+ } else {
44
+ throw new Error('Polygon requires either points array or cx/cy coordinates')
45
+ }
46
+
47
+ if (fill) {
48
+ polygon.fillColor = new paper.Color(fill)
49
+ } else {
50
+ polygon.fillColor = null
51
+ }
52
+
23
53
  if (stroke) {
24
54
  polygon.strokeColor = new paper.Color(stroke)
25
55
  polygon.strokeWidth = strokeWidth || 1
26
56
  }
57
+
27
58
  if (opacity !== undefined) polygon.opacity = opacity
28
59
 
29
60
  return { success: true, id: polygon.id, type: 'polygon' }
@@ -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
  // 设置透明度
@@ -3,7 +3,74 @@
3
3
  */
4
4
 
5
5
  const paper = require('paper')
6
- const { validateFont, getDefaultFont } = require('../fonts')
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
+
14
+ // Emoji Unicode 范围
15
+ const emojiRanges = [
16
+ /\u{1F600}-\u{1F64F}/u, // 表情符号
17
+ /\u{1F300}-\u{1F5FF}/u, // 符号和图片
18
+ /\u{1F680}-\u{1F6FF}/u, // 交通和地图
19
+ /\u{1F700}-\u{1F77F}/u, // 字母符号
20
+ /\u{1F780}-\u{1F7FF}/u, // 几何符号扩展
21
+ /\u{1F800}-\u{1F8FF}/u, // 箭头补充
22
+ /\u{1F900}-\u{1F9FF}/u, // 表情符号补充
23
+ /\u{1FA00}-\u{1FA6F}/u, // 棋牌符号
24
+ /\u{1FA70}-\u{1FAFF}/u, // 装饰符号
25
+ /\u{2600}-\u{26FF}/u, // 杂项符号
26
+ /\u{2700}-\u{27BF}/u, // 装饰符号
27
+ /[\u{1F000}-\u{1F02F}]/u, // 麻将牌
28
+ ]
29
+
30
+ for (const range of emojiRanges) {
31
+ if (range.test(text)) {
32
+ return true
33
+ }
34
+ }
35
+
36
+ // 简单检测:包含常见 emoji 字符
37
+ const simpleEmoji = /[\u{1F300}-\u{1F9FF}]/u
38
+ return simpleEmoji.test(text)
39
+ }
40
+
41
+ /**
42
+ * 获取适合的字体(考虑 emoji)
43
+ */
44
+ function getFontForText(requestedFont, text) {
45
+ const baseFont = validateFont(requestedFont) || getDefaultFont()
46
+
47
+ // 如果文本包含 emoji,尝试使用支持 emoji 的字体
48
+ if (containsEmoji(text)) {
49
+ const registeredFonts = getRegisteredFonts()
50
+
51
+ // 优先查找专门的 emoji 字体(多种命名方式)
52
+ const emojiFont = registeredFonts.find(f => {
53
+ if (!f) return false
54
+ const lower = f.toLowerCase()
55
+ return (
56
+ lower.includes('color emoji') || // Noto Color Emoji, Apple Color Emoji
57
+ lower.includes('noto emoji') || // Noto Emoji
58
+ lower.includes('segoe ui emoji') || // Windows emoji
59
+ lower.includes('symbola') || // Symbola
60
+ lower.includes('emoji') // 任何包含 emoji 的字体
61
+ )
62
+ })
63
+
64
+ if (emojiFont) {
65
+ console.log(`[poster] 检测到 emoji,使用字体: ${emojiFont}`)
66
+ return emojiFont
67
+ } else {
68
+ console.log(`[poster] 检测到 emoji,但未找到专用 emoji 字体,已注册字体: ${registeredFonts.join(', ')}`)
69
+ }
70
+ }
71
+
72
+ return baseFont
73
+ }
7
74
 
8
75
  /**
9
76
  * 添加文字
@@ -17,11 +84,13 @@ function addText(project, args) {
17
84
  shadow,
18
85
  } = args
19
86
 
87
+ const font = getFontForText(fontFamily, text)
88
+
20
89
  const textItem = new paper.PointText({
21
90
  point: [x, y],
22
91
  content: text,
23
92
  fontSize: fontSize || 48,
24
- fontFamily: validateFont(fontFamily) || getDefaultFont(),
93
+ fontFamily: font,
25
94
  fillColor: new paper.Color(color || '#ffffff'),
26
95
  justification: align || 'left',
27
96
  })