foliko 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/agents/code-assistant.json +17 -0
- package/.agent/agents/email-assistant.json +14 -0
- package/.agent/agents/file-assistant.json +18 -0
- package/.agent/agents/orchestrator-demo.md +53 -0
- package/.agent/agents/orchestrator.json +7 -0
- package/.agent/agents/poster-expert.md +228 -0
- package/.agent/agents/system-assistant.json +15 -0
- package/.agent/agents/web-assistant.json +12 -0
- package/.agent/data/default.json +5 -404
- package/.agent/data/email/processed-emails.json +1 -0
- package/.agent/data/plugins-state.json +173 -172
- package/.agent/data/scheduler/tasks.json +1 -0
- package/.agent/data/web/web-config.json +5 -0
- package/.agent/mcp_config.json +0 -14
- package/.agent/package.json +8 -0
- package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
- package/.agent/plugins/daytona/README.md +89 -0
- package/.agent/plugins/daytona/index.js +377 -0
- package/.agent/plugins/daytona/package.json +12 -0
- package/.agent/plugins/marknative/README.md +134 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/plugins/marknative/index.js +256 -0
- package/.agent/plugins/marknative/package.json +12 -0
- package/.agent/plugins/system-info/index.js +387 -0
- package/.agent/plugins/system-info/package.json +4 -0
- package/.agent/plugins/system-info/test.js +40 -0
- package/.agent/plugins/test-plugin.py +123 -0
- package/.agent/plugins/test_nested_plugin.py +85 -0
- package/.agent/plugins.json +11 -5
- package/.agent/python-scripts/test_sample.py +24 -0
- package/.agent/sessions/cli_default.json +96 -249
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
- package/.agent/skills/agent-browser/SKILL.md +311 -0
- package/.agent/skills/agent-browser/TEST_PLAN.md +200 -0
- package/.agent/skills/sysinfo/SKILL.md +38 -0
- package/.agent/skills/sysinfo/system-info.sh +130 -0
- package/.agent/skills/workflow/SKILL.md +324 -0
- package/.agent/test-agent.js +35 -0
- package/.agent/weixin.json +6 -0
- package/.agent/workflows/email-digest.json +50 -0
- package/.agent/workflows/file-backup.json +21 -0
- package/.agent/workflows/get-ip-notify.json +32 -0
- package/.agent/workflows/news-aggregator.json +93 -0
- package/.agent/workflows/news-dashboard-v2.json +94 -0
- package/.agent/workflows/notification-batch.json +32 -0
- package/.claude/settings.local.json +9 -1
- package/.env.example +56 -56
- package/README.md +441 -441
- package/cli/src/ui/chat-ui.js +32 -21
- package/foliko_poster.png +0 -0
- package/output/business_poster_final.png +0 -0
- package/output/emoji_test_v2.png +0 -0
- package/output/foliko-ai-launch-poster.png +0 -0
- package/output/foliko-poster.png +0 -0
- package/output/muji_style_poster.png +0 -0
- package/output/new_product_launch.png +0 -0
- package/output/news_poster_625yi_subsidy.png +0 -0
- package/output/tech_future_2026.png +0 -0
- package/output/tech_future_poster.png +0 -0
- package/package.json +1 -2
- package/plugins/default-plugins.js +58 -6
- package/plugins/extension-executor-plugin.js +8 -21
- package/plugins/python-plugin-loader.js +461 -40
- package/plugins/python-plugin-loader.js.bak +856 -0
- package/plugins/subagent-plugin.js +0 -121
- package/plugins/weixin-plugin.js +38 -29
- package/poster.png +0 -0
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/foliko-dev/SKILL.md +6 -0
- package/skills/python-plugin-dev/SKILL.md +124 -2
- package/src/core/agent-chat.js +220 -281
- package/src/core/agent.js +10 -0
- package/src/utils/plugin-helpers.js +22 -4
- package/system.md +1678 -1574
- package/test-scan.js +18 -0
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +0 -26
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +0 -97
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +0 -101
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +0 -31
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +0 -97
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +0 -59
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +0 -58
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +0 -258
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +0 -106
- package/.agent/ARCHITECTURE.md +0 -288
- package/.agent/agents/ambient-agent.md +0 -57
- package/.agent/agents/debugger.md +0 -55
- package/.agent/agents/email-assistant.md +0 -49
- package/.agent/agents/file-manager.md +0 -42
- package/.agent/agents/python-developer.md +0 -60
- package/.agent/agents/scheduler.md +0 -59
- package/.agent/agents/web-developer.md +0 -45
- package/.agent/data/puppeteer-sessions/undefined.json +0 -6
- 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/memory/feedback/mnrdvj5i-ca3dkd.md +0 -9
- package/.agent/memory/feedback/mnre365e-7s4zax.md +0 -9
- package/.agent/memory/feedback/mnre36jn-nkfgmp.md +0 -9
- package/.agent/memory/feedback/mnre3805-kjiq6h.md +0 -9
- package/.agent/memory/feedback/mnsf66kp-b10rcd.md +0 -9
- package/.agent/memory/feedback/mnsi3sz8-p5g2cw.md +0 -9
- package/.agent/memory/feedback/mnsibe47-sv2ni1.md +0 -9
- package/.agent/memory/feedback/mnsic89w-nn228o.md +0 -9
- package/.agent/memory/feedback/mnsj1xe9-x83ba0.md +0 -9
- package/.agent/memory/feedback/mnsj21iv-wnwelx.md +0 -9
- package/.agent/memory/feedback/mnsj2g4a-cog7a2.md +0 -9
- package/.agent/memory/feedback/mnsj4js7-lktjp6.md +0 -9
- package/.agent/memory/feedback/mnsj5d4y-uglwvp.md +0 -9
- package/.agent/memory/feedback/mnslkuo9-uous66.md +0 -24
- package/.agent/memory/feedback/mnsm3vq0-megoil.md +0 -9
- package/.agent/memory/feedback/mnsnn5x2-sxcihd.md +0 -9
- package/.agent/memory/feedback/mnsnq17s-nabrn9.md +0 -9
- package/.agent/memory/feedback/mnsnybet-wz7rn3.md +0 -9
- package/.agent/memory/feedback/mnsrw0s7-7s9e30.md +0 -9
- package/.agent/memory/feedback/mnu5hpnd-tlm16q.md +0 -9
- package/.agent/memory/feedback/mnu60uqe-xuoxp4.md +0 -9
- package/.agent/memory/project/mnqx54u5-loqtoe.md +0 -9
- package/.agent/memory/project/mnqx84cv-mx6dmd.md +0 -9
- package/.agent/memory/project/mnsacuyr-hgtk5n.md +0 -20
- package/.agent/memory/project/mnu5hy2x-bjsg7u.md +0 -9
- package/.agent/memory/reference/mnre3cww-penbo1.md +0 -9
- package/.agent/memory/reference/mns9wn48-luerua.md +0 -14
- package/.agent/memory/reference/mns9yz5c-thc2s0.md +0 -16
- package/.agent/memory/reference/mnsfy4um-910f1o.md +0 -23
- package/.agent/memory/reference/mnsg37dp-lmfj18.md +0 -32
- package/.agent/memory/reference/mnsll60q-0j911u.md +0 -36
- package/.agent/memory/reference/mnsmlb5y-nej31u.md +0 -16
- package/.agent/memory/reference/mnssle72-yrot96.md +0 -9
- package/.agent/memory/user/mnsfuon6-l416q1.md +0 -21
- package/.agent/memory/user/mnsg9kut-95m7rf.md +0 -20
- package/.agent/memory/user/mnu2eo1v-yy6fhe.md +0 -9
- package/.agent/memory/user/mnu2etuo-8u8jk8.md +0 -9
- package/.agent/plugins/poster-plugin/README.md +0 -304
- package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/Symbola_hint.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +0 -13
- package/.agent/plugins/poster-plugin/package.json +0 -29
- package/.agent/plugins/poster-plugin/src/canvas.js +0 -232
- package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
- package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
- package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -123
- package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -154
- package/.agent/plugins/poster-plugin/src/components/button.js +0 -168
- package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
- package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
- package/.agent/plugins/poster-plugin/src/components/columns.js +0 -121
- package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
- package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
- package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
- package/.agent/plugins/poster-plugin/src/components/grid.js +0 -130
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -145
- package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -205
- package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
- package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
- package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
- package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
- package/.agent/plugins/poster-plugin/src/components/quote.js +0 -169
- package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -197
- package/.agent/plugins/poster-plugin/src/components/seal.js +0 -148
- package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
- package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
- package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
- package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
- package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
- package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -54
- package/.agent/plugins/poster-plugin/src/composer.js +0 -2314
- package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -69
- package/.agent/plugins/poster-plugin/src/elements/background.js +0 -99
- package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
- package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
- package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
- package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -283
- package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
- package/.agent/plugins/poster-plugin/src/elements/text.js +0 -112
- package/.agent/plugins/poster-plugin/src/fonts.js +0 -674
- package/.agent/plugins/poster-plugin/src/index.js +0 -2126
- package/.agent/plugins/poster-plugin/src/presets.js +0 -36
- package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
- package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
- package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
- package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
- package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
- package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
- package/.agent/plugins/poster-plugin/yarn.lock +0 -837
- package/.agent/plugins/puppeteer-plugin/README.md +0 -147
- package/.agent/plugins/puppeteer-plugin/index.js +0 -1422
- package/.agent/plugins/puppeteer-plugin/package.json +0 -9
- package/.agent/rules/GEMINI.md +0 -273
- package/.agent/rules/allow-rule.md +0 -77
- package/.agent/rules/log-rule.md +0 -83
- package/.agent/rules/security-rule.md +0 -93
- package/.agent/scripts/auto_preview.py +0 -148
- package/.agent/scripts/checklist.py +0 -217
- package/.agent/scripts/session_manager.py +0 -120
- package/.agent/scripts/verify_all.py +0 -327
- package/.agent/skills/api-patterns/SKILL.md +0 -81
- package/.agent/skills/api-patterns/api-style.md +0 -42
- package/.agent/skills/api-patterns/auth.md +0 -24
- package/.agent/skills/api-patterns/documentation.md +0 -26
- package/.agent/skills/api-patterns/graphql.md +0 -41
- package/.agent/skills/api-patterns/rate-limiting.md +0 -31
- package/.agent/skills/api-patterns/response.md +0 -37
- package/.agent/skills/api-patterns/rest.md +0 -40
- package/.agent/skills/api-patterns/scripts/api_validator.py +0 -211
- package/.agent/skills/api-patterns/security-testing.md +0 -122
- package/.agent/skills/api-patterns/trpc.md +0 -41
- package/.agent/skills/api-patterns/versioning.md +0 -22
- package/.agent/skills/app-builder/SKILL.md +0 -75
- package/.agent/skills/app-builder/agent-coordination.md +0 -71
- package/.agent/skills/app-builder/feature-building.md +0 -53
- package/.agent/skills/app-builder/project-detection.md +0 -34
- package/.agent/skills/app-builder/scaffolding.md +0 -118
- package/.agent/skills/app-builder/tech-stack.md +0 -40
- package/.agent/skills/app-builder/templates/SKILL.md +0 -39
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +0 -76
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +0 -92
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +0 -88
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +0 -88
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +0 -83
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +0 -90
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +0 -90
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +0 -122
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +0 -122
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +0 -169
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +0 -134
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +0 -83
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +0 -119
- package/.agent/skills/architecture/SKILL.md +0 -55
- package/.agent/skills/architecture/context-discovery.md +0 -43
- package/.agent/skills/architecture/examples.md +0 -94
- package/.agent/skills/architecture/pattern-selection.md +0 -68
- package/.agent/skills/architecture/patterns-reference.md +0 -50
- package/.agent/skills/architecture/trade-off-analysis.md +0 -77
- package/.agent/skills/clean-code/SKILL.md +0 -201
- package/.agent/skills/doc.md +0 -177
- package/.agent/skills/frontend-design/SKILL.md +0 -418
- package/.agent/skills/frontend-design/animation-guide.md +0 -331
- package/.agent/skills/frontend-design/color-system.md +0 -311
- package/.agent/skills/frontend-design/decision-trees.md +0 -418
- package/.agent/skills/frontend-design/motion-graphics.md +0 -306
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +0 -183
- package/.agent/skills/frontend-design/scripts/ux_audit.py +0 -722
- package/.agent/skills/frontend-design/typography-system.md +0 -345
- package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
- package/.agent/skills/frontend-design/visual-effects.md +0 -383
- package/.agent/skills/i18n-localization/SKILL.md +0 -154
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +0 -241
- package/.agent/skills/mcp-builder/SKILL.md +0 -176
- package/.agent/skills/poster-design/SKILL.md +0 -385
- package/.agent/skills/web-design-guidelines/SKILL.md +0 -57
- package/.agent/workflows/brainstorm.md +0 -113
- package/.agent/workflows/create.md +0 -59
- package/.agent/workflows/debug.md +0 -103
- package/.agent/workflows/deploy.md +0 -176
- package/.agent/workflows/enhance.md +0 -63
- package/.agent/workflows/orchestrate.md +0 -237
- package/.agent/workflows/plan.md +0 -89
- package/.agent/workflows/preview.md +0 -81
- package/.agent/workflows/simple-test.md +0 -42
- package/.agent/workflows/status.md +0 -86
- package/.agent/workflows/structured-orchestrate.md +0 -180
- package/.agent/workflows/test.md +0 -144
- package/.agent/workflows/ui-ux-pro-max.md +0 -296
- package/outputs/emoji-font-showcase.png +0 -0
- package/outputs/foliko-muji-style.png +0 -0
- /package/.agent/plugins/{poster-plugin → marknative}/fonts/SegoeUI Emoji.ttf +0 -0
|
@@ -1,674 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 字体管理模块 - 支持多字体 fallback
|
|
3
|
-
* 使用 @napi-rs/canvas 原生 API 解决中文乱码问题
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const path = require('path')
|
|
7
|
-
const fs = require('fs')
|
|
8
|
-
|
|
9
|
-
// 导入 @napi-rs/canvas 原生 API
|
|
10
|
-
let GlobalFonts = null
|
|
11
|
-
try {
|
|
12
|
-
const canvas = require('@napi-rs/canvas')
|
|
13
|
-
GlobalFonts = canvas.GlobalFonts
|
|
14
|
-
} catch (e) {
|
|
15
|
-
console.warn('[poster] @napi-rs/canvas 未安装,字体注册可能受限')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// 兼容旧的 canvas API(备用)
|
|
19
|
-
let legacyRegisterFont = null
|
|
20
|
-
try {
|
|
21
|
-
legacyRegisterFont = require('canvas').registerFont
|
|
22
|
-
} catch (e) {
|
|
23
|
-
// ignore
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 已注册的字体
|
|
27
|
-
const registeredFonts = new Map()
|
|
28
|
-
|
|
29
|
-
// 字体信息缓存(用于返回详细列表)
|
|
30
|
-
let fontInfoList = []
|
|
31
|
-
|
|
32
|
-
// Emoji 字体名称映射(用于统一识别)
|
|
33
|
-
const emojiFontMappings = {
|
|
34
|
-
'NotoColorEmoji': 'Noto Color Emoji',
|
|
35
|
-
'NotoColorEmoji-Regular': 'Noto Color Emoji',
|
|
36
|
-
'Noto Emoji': 'Noto Emoji',
|
|
37
|
-
'NotoEmoji-Regular': 'Noto Emoji',
|
|
38
|
-
'Apple Color Emoji': 'Apple Color Emoji',
|
|
39
|
-
'Segoe UI Emoji': 'Segoe UI Emoji',
|
|
40
|
-
'SegoeUI Emoji': 'Segoe UI Emoji',
|
|
41
|
-
'Symbola': 'Symbola',
|
|
42
|
-
'Symbola_hint': 'Symbola',
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 中文字体别名映射表 - 将请求的字体映射到实际可用的字体
|
|
46
|
-
const chineseFontMappings = {
|
|
47
|
-
// 黑体系列
|
|
48
|
-
'SimHei': 'Microsoft YaHei',
|
|
49
|
-
'黑体': 'Microsoft YaHei',
|
|
50
|
-
'Hei': 'Microsoft YaHei',
|
|
51
|
-
'Microsoft YaHei': 'Microsoft YaHei',
|
|
52
|
-
'微软雅黑': 'Microsoft YaHei',
|
|
53
|
-
'msyh': 'Microsoft YaHei',
|
|
54
|
-
'Source Han Sans CN': 'Source Han Sans CN',
|
|
55
|
-
'思源黑体': 'Noto Sans CJK SC',
|
|
56
|
-
'Noto Sans CJK': 'Noto Sans CJK SC',
|
|
57
|
-
'Noto Sans CJK SC': 'Noto Sans CJK SC',
|
|
58
|
-
'Noto Sans': 'Noto Sans CJK SC',
|
|
59
|
-
|
|
60
|
-
// 宋体系列
|
|
61
|
-
'SimSun': 'SimSun',
|
|
62
|
-
'宋体': 'SimSun',
|
|
63
|
-
'Song': 'SimSun',
|
|
64
|
-
'SimSun_GB2312': 'SimSun',
|
|
65
|
-
'方正宋体': 'Noto Serif CJK SC',
|
|
66
|
-
'Noto Serif CJK': 'Noto Serif CJK SC',
|
|
67
|
-
'Noto Serif CJK SC': 'Noto Serif CJK SC',
|
|
68
|
-
|
|
69
|
-
// 楷体系列
|
|
70
|
-
'Kai': 'LXGW WenKai',
|
|
71
|
-
'楷体': 'LXGW WenKai',
|
|
72
|
-
'Kaiti': 'LXGW WenKai',
|
|
73
|
-
'STKaiti': 'LXGW WenKai',
|
|
74
|
-
'华文楷体': 'LXGW WenKai',
|
|
75
|
-
'楷书': 'LXGW WenKai',
|
|
76
|
-
'LXGW WenKai': 'LXGW WenKai',
|
|
77
|
-
'LXGWWenKai': 'LXGW WenKai',
|
|
78
|
-
'LXGWWenKai-Regular': 'LXGW WenKai',
|
|
79
|
-
'霞鹜文楷': 'LXGW WenKai',
|
|
80
|
-
|
|
81
|
-
// 仿宋系列
|
|
82
|
-
'FangSong': 'SimSun',
|
|
83
|
-
'仿宋': 'SimSun',
|
|
84
|
-
'FangSong_GB2312': 'SimSun',
|
|
85
|
-
'方正仿宋': 'Noto Serif CJK SC',
|
|
86
|
-
|
|
87
|
-
// 幼圆
|
|
88
|
-
'YouYuan': 'Microsoft YaHei',
|
|
89
|
-
'幼圆': 'Microsoft YaHei',
|
|
90
|
-
'圆体': 'Microsoft YaHei',
|
|
91
|
-
|
|
92
|
-
// 华文系列
|
|
93
|
-
'STHeiti': 'Microsoft YaHei',
|
|
94
|
-
'华文黑体': 'Microsoft YaHei',
|
|
95
|
-
'STSong': 'SimSun',
|
|
96
|
-
'华文宋体': 'SimSun',
|
|
97
|
-
'STSongti': 'SimSun',
|
|
98
|
-
'华文宋体_GB2312': 'SimSun',
|
|
99
|
-
'STZhongsong': 'Noto Serif CJK SC',
|
|
100
|
-
'华文中宋': 'Noto Serif CJK SC',
|
|
101
|
-
'STCaiyun': 'Microsoft YaHei',
|
|
102
|
-
'华文彩云': 'Microsoft YaHei',
|
|
103
|
-
'STXingkai': 'Microsoft YaHei',
|
|
104
|
-
'华文行楷': 'Microsoft YaHei',
|
|
105
|
-
'STXinwei': 'Microsoft YaHei',
|
|
106
|
-
'华文新魏': 'Microsoft YaHei',
|
|
107
|
-
'STLiti': 'Microsoft YaHei',
|
|
108
|
-
'华文隶书': 'Microsoft YaHei',
|
|
109
|
-
|
|
110
|
-
// 其他常见中文别名
|
|
111
|
-
'sans-serif': 'Microsoft YaHei',
|
|
112
|
-
'serif': 'SimSun',
|
|
113
|
-
'monospace': 'Consolas',
|
|
114
|
-
'PingFang': 'Microsoft YaHei',
|
|
115
|
-
'PingFang SC': 'Microsoft YaHei',
|
|
116
|
-
'Hiragino Sans GB': 'Microsoft YaHei',
|
|
117
|
-
'冬青黑体': 'Microsoft YaHei',
|
|
118
|
-
'WenQuanYi': 'Microsoft YaHei',
|
|
119
|
-
'文泉驿': 'Microsoft YaHei',
|
|
120
|
-
'WenQuanYi Micro Hei': 'Microsoft YaHei',
|
|
121
|
-
'WenQuanYi Zen Hei': 'Microsoft YaHei',
|
|
122
|
-
'AR PL': 'Microsoft YaHei',
|
|
123
|
-
'AR PL UKai': 'SimSun',
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// 默认字体信息
|
|
127
|
-
let defaultFont = {
|
|
128
|
-
name: 'Microsoft YaHei',
|
|
129
|
-
family: 'Microsoft YaHei',
|
|
130
|
-
path: null,
|
|
131
|
-
weight: 'normal',
|
|
132
|
-
style: 'normal',
|
|
133
|
-
isDefault: true,
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 使用 @napi-rs/canvas 原生 API 注册字体
|
|
138
|
-
*/
|
|
139
|
-
function registerFontWithGlobalFonts(fontPath, fontFamily, options = {}) {
|
|
140
|
-
if (!GlobalFonts) return false
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const absolutePath = path.resolve(fontPath)
|
|
144
|
-
|
|
145
|
-
// 使用 registerFromPath 注册字体
|
|
146
|
-
GlobalFonts.registerFromPath(absolutePath, fontFamily)
|
|
147
|
-
|
|
148
|
-
return true
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.log(`[poster] GlobalFonts 注册字体失败: ${fontFamily}`, e.message)
|
|
151
|
-
return false
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 使用 legacy canvas API 注册字体(备用)
|
|
157
|
-
*/
|
|
158
|
-
function registerFontWithLegacy(fontPath, fontFamily, options = {}) {
|
|
159
|
-
if (!legacyRegisterFont) return false
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
legacyRegisterFont(fontPath, {
|
|
163
|
-
family: fontFamily,
|
|
164
|
-
weight: options.weight || 'normal',
|
|
165
|
-
style: options.style || 'normal',
|
|
166
|
-
})
|
|
167
|
-
return true
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.log(`[poster] Legacy 注册字体失败: ${fontFamily}`, e.message)
|
|
170
|
-
return false
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* 注册字体文件 - 双 API 策略
|
|
176
|
-
*/
|
|
177
|
-
function registerFontFile(fontPath, fontFamily, options = {}) {
|
|
178
|
-
if (!fontFamily) return false
|
|
179
|
-
|
|
180
|
-
// 避免重复注册
|
|
181
|
-
if (registeredFonts.has(fontFamily)) {
|
|
182
|
-
return true
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
if (!fs.existsSync(fontPath)) {
|
|
187
|
-
return false
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let success = false
|
|
191
|
-
|
|
192
|
-
// 优先使用 @napi-rs/canvas 原生 API
|
|
193
|
-
if (GlobalFonts) {
|
|
194
|
-
success = registerFontWithGlobalFonts(fontPath, fontFamily, options)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// 同时使用 legacy API 保持兼容性(paper.js 可能需要)
|
|
198
|
-
if (!success && legacyRegisterFont) {
|
|
199
|
-
success = registerFontWithLegacy(fontPath, fontFamily, options)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (success) {
|
|
203
|
-
const fontInfo = {
|
|
204
|
-
name: fontFamily,
|
|
205
|
-
family: fontFamily,
|
|
206
|
-
path: path.resolve(fontPath),
|
|
207
|
-
weight: options.weight || 'normal',
|
|
208
|
-
style: options.style || 'normal',
|
|
209
|
-
isDefault: false,
|
|
210
|
-
isEmoji: isEmojiFont(fontFamily),
|
|
211
|
-
source: options.source || 'unknown',
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
registeredFonts.set(fontFamily, fontInfo)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return success
|
|
218
|
-
} catch (e) {
|
|
219
|
-
console.log(`[poster] 注册字体失败: ${fontFamily}`, e.message)
|
|
220
|
-
return false
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* 判断是否为 Emoji 字体
|
|
226
|
-
*/
|
|
227
|
-
function isEmojiFont(fontName) {
|
|
228
|
-
if (!fontName) return false
|
|
229
|
-
const lower = fontName.toLowerCase()
|
|
230
|
-
return (
|
|
231
|
-
lower.includes('emoji') ||
|
|
232
|
-
lower.includes('color') ||
|
|
233
|
-
lower.includes('symbola')
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* 获取插件字体目录
|
|
239
|
-
*/
|
|
240
|
-
function getPluginFontsDir() {
|
|
241
|
-
// 插件目录
|
|
242
|
-
const pluginDir = path.join(__dirname, '..')
|
|
243
|
-
const pluginFontsDir = path.join(pluginDir, 'fonts')
|
|
244
|
-
|
|
245
|
-
// 也尝试从工作目录
|
|
246
|
-
const cwdFontsDir = path.join(process.cwd(), '.agent', 'plugins', 'poster-plugin', 'fonts')
|
|
247
|
-
|
|
248
|
-
if (fs.existsSync(pluginFontsDir)) {
|
|
249
|
-
return pluginFontsDir
|
|
250
|
-
}
|
|
251
|
-
if (fs.existsSync(cwdFontsDir)) {
|
|
252
|
-
return cwdFontsDir
|
|
253
|
-
}
|
|
254
|
-
return pluginFontsDir
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* 获取系统字体路径
|
|
259
|
-
*/
|
|
260
|
-
function getSystemFontPaths() {
|
|
261
|
-
const isWin = process.platform === 'win32'
|
|
262
|
-
const isMac = process.platform === 'darwin'
|
|
263
|
-
|
|
264
|
-
const fonts = []
|
|
265
|
-
|
|
266
|
-
if (isWin) {
|
|
267
|
-
const winDir = 'C:\\Windows\\Fonts'
|
|
268
|
-
fonts.push(
|
|
269
|
-
// 中文首选 - 微软雅黑
|
|
270
|
-
{ path: `${winDir}\\msyh.ttc`, family: 'Microsoft YaHei', weight: 'normal' },
|
|
271
|
-
{ path: `${winDir}\\msyhbd.ttc`, family: 'Microsoft YaHei', weight: 'bold' },
|
|
272
|
-
// 备选中文字体
|
|
273
|
-
{ path: `${winDir}\\simhei.ttf`, family: 'SimHei', weight: 'normal' },
|
|
274
|
-
{ path: `${winDir}\\simsun.ttc`, family: 'SimSun', weight: 'normal' },
|
|
275
|
-
{ path: `${winDir}\\simkai.ttf`, family: 'KaiTi', weight: 'normal' },
|
|
276
|
-
// 常用西文字体
|
|
277
|
-
{ path: `${winDir}\\arial.ttf`, family: 'Arial', weight: 'normal' },
|
|
278
|
-
{ path: `${winDir}\\ariali.ttf`, family: 'Arial', weight: 'normal', style: 'italic' },
|
|
279
|
-
{ path: `${winDir}\\arialbi.ttf`, family: 'Arial', weight: 'bold', style: 'italic' },
|
|
280
|
-
{ path: `${winDir}\\arialbd.ttf`, family: 'Arial', weight: 'bold' },
|
|
281
|
-
{ path: `${winDir}\\times.ttf`, family: 'Times New Roman', weight: 'normal' },
|
|
282
|
-
{ path: `${winDir}\\timesi.ttf`, family: 'Times New Roman', weight: 'normal', style: 'italic' },
|
|
283
|
-
{ path: `${winDir}\\timesbi.ttf`, family: 'Times New Roman', weight: 'bold', style: 'italic' },
|
|
284
|
-
{ path: `${winDir}\\timesbd.ttf`, family: 'Times New Roman', weight: 'bold' },
|
|
285
|
-
{ path: `${winDir}\\consola.ttf`, family: 'Consolas', weight: 'normal' },
|
|
286
|
-
{ path: `${winDir}\\calibri.ttf`, family: 'Calibri', weight: 'normal' },
|
|
287
|
-
// Emoji 字体
|
|
288
|
-
{ path: `${winDir}\\seguiemj.ttf`, family: 'Segoe UI Emoji', weight: 'normal' },
|
|
289
|
-
)
|
|
290
|
-
} else if (isMac) {
|
|
291
|
-
fonts.push(
|
|
292
|
-
// macOS 中文字体
|
|
293
|
-
{ path: '/System/Library/Fonts/PingFang.ttc', family: 'PingFang SC', weight: 'normal' },
|
|
294
|
-
{ path: '/System/Library/Fonts/STHeiti Light.ttc', family: 'Heiti SC', weight: 'normal' },
|
|
295
|
-
{ path: '/System/Library/Fonts/STHeiti Medium.ttc', family: 'Heiti SC', weight: 'bold' },
|
|
296
|
-
// Emoji
|
|
297
|
-
{ path: '/System/Library/Fonts/Apple Color Emoji.ttc', family: 'Apple Color Emoji', weight: 'normal' },
|
|
298
|
-
// 常用西文
|
|
299
|
-
{ path: '/System/Library/Fonts/Helvetica.ttc', family: 'Helvetica', weight: 'normal' },
|
|
300
|
-
{ path: '/System/Library/Fonts/Times.ttc', family: 'Times New Roman', weight: 'normal' },
|
|
301
|
-
)
|
|
302
|
-
} else {
|
|
303
|
-
// Linux
|
|
304
|
-
fonts.push(
|
|
305
|
-
// 系统 CJK 字体
|
|
306
|
-
{ path: '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', family: 'Noto Sans CJK SC', weight: 'normal' },
|
|
307
|
-
{ path: '/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc', family: 'Noto Sans CJK SC', weight: 'bold' },
|
|
308
|
-
{ path: '/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc', family: 'Noto Serif CJK SC', weight: 'normal' },
|
|
309
|
-
// 备选路径
|
|
310
|
-
{ path: '/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc', family: 'Noto Sans CJK SC', weight: 'normal' },
|
|
311
|
-
{ path: '/usr/share/fonts/truetype/chinese/NotoSansCJK-Regular.ttc', family: 'Noto Sans CJK SC', weight: 'normal' },
|
|
312
|
-
// 西文字体
|
|
313
|
-
{ path: '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', family: 'DejaVu Sans', weight: 'normal' },
|
|
314
|
-
{ path: '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', family: 'DejaVu Sans', weight: 'bold' },
|
|
315
|
-
// Emoji
|
|
316
|
-
{ path: '/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf', family: 'Noto Color Emoji', weight: 'normal' },
|
|
317
|
-
{ path: '/usr/share/fonts/truetype/noto/NotoEmoji-Regular.ttf', family: 'Noto Emoji', weight: 'normal' },
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return fonts
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* 初始化字体
|
|
326
|
-
*/
|
|
327
|
-
function initFonts() {
|
|
328
|
-
// 清空之前的字体信息
|
|
329
|
-
fontInfoList = []
|
|
330
|
-
registeredFonts.clear()
|
|
331
|
-
|
|
332
|
-
console.log('[poster] 初始化字体系统...')
|
|
333
|
-
console.log(`[poster] 平台: ${process.platform}`)
|
|
334
|
-
|
|
335
|
-
// 1. 优先加载插件自带的字体
|
|
336
|
-
const pluginFontsDir = getPluginFontsDir()
|
|
337
|
-
console.log(`[poster] 插件字体目录: ${pluginFontsDir}`)
|
|
338
|
-
|
|
339
|
-
if (fs.existsSync(pluginFontsDir)) {
|
|
340
|
-
const fontFiles = fs.readdirSync(pluginFontsDir)
|
|
341
|
-
|
|
342
|
-
for (const file of fontFiles) {
|
|
343
|
-
if (!file.endsWith('.ttf') && !file.endsWith('.otf') && !file.endsWith('.ttc')) {
|
|
344
|
-
continue
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const fontPath = path.join(pluginFontsDir, file)
|
|
348
|
-
let fontName = path.basename(file, path.extname(file))
|
|
349
|
-
|
|
350
|
-
// 检查是否为 Emoji 字体
|
|
351
|
-
if (isEmojiFont(fontName)) {
|
|
352
|
-
fontName = emojiFontMappings[fontName] || 'Noto Color Emoji'
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// 特殊处理中文文件名
|
|
356
|
-
if (fontName === '微软雅黑' || fontName === '微软雅黑粗体') {
|
|
357
|
-
fontName = fontName.includes('粗体') ? 'Microsoft YaHei' : 'Microsoft YaHei'
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const options = {
|
|
361
|
-
source: 'plugin',
|
|
362
|
-
weight: fontName.includes('Bold') || fontName.includes('粗体') ? 'bold' : 'normal',
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (registerFontFile(fontPath, fontName, options)) {
|
|
366
|
-
console.log(`[poster] 已注册插件字体: ${fontName} (${file})`)
|
|
367
|
-
|
|
368
|
-
// 微软雅黑设为默认
|
|
369
|
-
if ((fontName === 'Microsoft YaHei' || fontName === '微软雅黑') && options.weight === 'normal') {
|
|
370
|
-
defaultFont = {
|
|
371
|
-
name: 'Microsoft YaHei',
|
|
372
|
-
family: 'Microsoft YaHei',
|
|
373
|
-
path: fontPath,
|
|
374
|
-
weight: 'normal',
|
|
375
|
-
style: 'normal',
|
|
376
|
-
isDefault: true,
|
|
377
|
-
source: 'plugin',
|
|
378
|
-
}
|
|
379
|
-
console.log(`[poster] 设为默认字体: Microsoft YaHei`)
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
fontInfoList.push({
|
|
383
|
-
name: fontName,
|
|
384
|
-
family: fontName,
|
|
385
|
-
path: fontPath,
|
|
386
|
-
source: 'plugin',
|
|
387
|
-
isEmoji: isEmojiFont(fontName),
|
|
388
|
-
})
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// 2. 加载系统字体
|
|
394
|
-
console.log('[poster] 加载系统字体...')
|
|
395
|
-
const systemFonts = getSystemFontPaths()
|
|
396
|
-
|
|
397
|
-
for (const font of systemFonts) {
|
|
398
|
-
if (registerFontFile(font.path, font.family, { weight: font.weight, style: font.style, source: 'system' })) {
|
|
399
|
-
if (!fontInfoList.find(f => f.name === font.family)) {
|
|
400
|
-
fontInfoList.push({
|
|
401
|
-
name: font.family,
|
|
402
|
-
family: font.family,
|
|
403
|
-
path: font.path,
|
|
404
|
-
source: 'system',
|
|
405
|
-
isEmoji: isEmojiFont(font.family),
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// 3. 如果没有默认字体,尝试从系统字体中选择
|
|
412
|
-
if (!defaultFont.path) {
|
|
413
|
-
// 优先选择支持中文的系统字体
|
|
414
|
-
const chineseFontCandidates = ['Microsoft YaHei', 'SimHei', 'PingFang SC', 'Noto Sans CJK SC', 'Source Han Sans CN']
|
|
415
|
-
|
|
416
|
-
for (const candidate of chineseFontCandidates) {
|
|
417
|
-
if (registeredFonts.has(candidate)) {
|
|
418
|
-
const info = registeredFonts.get(candidate)
|
|
419
|
-
defaultFont = {
|
|
420
|
-
name: candidate,
|
|
421
|
-
family: candidate,
|
|
422
|
-
path: info.path,
|
|
423
|
-
weight: 'normal',
|
|
424
|
-
style: 'normal',
|
|
425
|
-
isDefault: true,
|
|
426
|
-
source: 'system',
|
|
427
|
-
}
|
|
428
|
-
console.log(`[poster] 设为默认字体: ${candidate}`)
|
|
429
|
-
break
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// 4. 确保有回退字体
|
|
435
|
-
const fallbackFonts = [
|
|
436
|
-
{ family: 'Arial', weight: 'normal', path: 'C:\\Windows\\Fonts\\arial.ttf' },
|
|
437
|
-
{ family: 'Helvetica', weight: 'normal', path: '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf' },
|
|
438
|
-
]
|
|
439
|
-
|
|
440
|
-
for (const fb of fallbackFonts) {
|
|
441
|
-
if (!registeredFonts.has(fb.family) && fs.existsSync(fb.path)) {
|
|
442
|
-
registerFontFile(fb.path, fb.family, { weight: fb.weight, source: 'fallback' })
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// 5. 最终回退
|
|
447
|
-
if (!registeredFonts.has('sans-serif')) {
|
|
448
|
-
registeredFonts.set('sans-serif', {
|
|
449
|
-
name: 'sans-serif',
|
|
450
|
-
family: 'sans-serif',
|
|
451
|
-
path: null,
|
|
452
|
-
isDefault: false,
|
|
453
|
-
source: 'fallback',
|
|
454
|
-
})
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if (!defaultFont.path) {
|
|
458
|
-
defaultFont = {
|
|
459
|
-
name: 'sans-serif',
|
|
460
|
-
family: 'sans-serif',
|
|
461
|
-
path: null,
|
|
462
|
-
weight: 'normal',
|
|
463
|
-
style: 'normal',
|
|
464
|
-
isDefault: true,
|
|
465
|
-
source: 'fallback',
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// 打印注册结果
|
|
470
|
-
console.log(`[poster] 字体注册完成,共 ${registeredFonts.size} 个字体`)
|
|
471
|
-
console.log(`[poster] 默认字体: ${defaultFont.name} (${defaultFont.source})`)
|
|
472
|
-
|
|
473
|
-
// 打印中文字体列表
|
|
474
|
-
const cjkFonts = Array.from(registeredFonts.keys()).filter(f =>
|
|
475
|
-
f.includes('CJK') || f.includes('Han') || f.includes('YaHei') ||
|
|
476
|
-
f.includes('Hei') || f.includes('Song') || f.includes('Kai') ||
|
|
477
|
-
f.includes('Fang') || f.includes('Yuan') || f.includes('PingFang')
|
|
478
|
-
)
|
|
479
|
-
if (cjkFonts.length > 0) {
|
|
480
|
-
console.log(`[poster] 已注册 CJK 字体: ${cjkFonts.join(', ')}`)
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
refreshFontList()
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* 刷新字体列表缓存
|
|
488
|
-
*/
|
|
489
|
-
function refreshFontList() {
|
|
490
|
-
fontInfoList = []
|
|
491
|
-
for (const [name, info] of registeredFonts) {
|
|
492
|
-
fontInfoList.push({
|
|
493
|
-
name: name,
|
|
494
|
-
family: info.family || name,
|
|
495
|
-
path: info.path,
|
|
496
|
-
source: info.source || 'unknown',
|
|
497
|
-
isEmoji: info.isEmoji || isEmojiFont(name),
|
|
498
|
-
isDefault: info.isDefault || (name === defaultFont.name),
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* 验证字体是否可用 - 支持字体别名映射
|
|
505
|
-
*/
|
|
506
|
-
function validateFont(fontFamily) {
|
|
507
|
-
if (!fontFamily) return defaultFont.name
|
|
508
|
-
|
|
509
|
-
// 直接匹配
|
|
510
|
-
if (registeredFonts.has(fontFamily)) return fontFamily
|
|
511
|
-
|
|
512
|
-
// 别名映射
|
|
513
|
-
const mapped = chineseFontMappings[fontFamily]
|
|
514
|
-
if (mapped && registeredFonts.has(mapped)) {
|
|
515
|
-
return mapped
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// 大小写不敏感匹配
|
|
519
|
-
const lower = fontFamily.toLowerCase()
|
|
520
|
-
for (const [name] of registeredFonts) {
|
|
521
|
-
if (name.toLowerCase() === lower) return name
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// 如果请求的是中文,尝试返回中文字体
|
|
525
|
-
if (/[\u4e00-\u9fff]/.test(fontFamily) || isCommonChineseFontName(fontFamily)) {
|
|
526
|
-
const chineseFonts = ['Microsoft YaHei', 'PingFang SC', 'SimHei', 'Noto Sans CJK SC']
|
|
527
|
-
for (const cf of chineseFonts) {
|
|
528
|
-
if (registeredFonts.has(cf)) return cf
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
return defaultFont.name
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* 判断是否为常见中文字体名称
|
|
537
|
-
*/
|
|
538
|
-
function isCommonChineseFontName(name) {
|
|
539
|
-
if (!name) return false
|
|
540
|
-
const lower = name.toLowerCase()
|
|
541
|
-
const keywords = [
|
|
542
|
-
'hei', 'song', 'kai', 'fang', 'yuan', '仿', '宋', '楷', '黑', '圆',
|
|
543
|
-
'微软', '思源', 'noto', 'source', 'wqy', '文泉', 'pingfang', 'hiragino'
|
|
544
|
-
]
|
|
545
|
-
return keywords.some(kw => lower.includes(kw.toLowerCase()))
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* 获取已注册字体列表
|
|
550
|
-
*/
|
|
551
|
-
function listAllFonts() {
|
|
552
|
-
refreshFontList()
|
|
553
|
-
|
|
554
|
-
const regularFonts = fontInfoList.filter(f => !f.isEmoji)
|
|
555
|
-
const emojiFonts = fontInfoList.filter(f => f.isEmoji)
|
|
556
|
-
|
|
557
|
-
return {
|
|
558
|
-
success: true,
|
|
559
|
-
default: defaultFont,
|
|
560
|
-
fonts: fontInfoList,
|
|
561
|
-
regular: regularFonts.map(f => f.name),
|
|
562
|
-
emoji: emojiFonts.map(f => f.name),
|
|
563
|
-
total: fontInfoList.length,
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* 获取默认字体
|
|
569
|
-
*/
|
|
570
|
-
function getDefaultFont() {
|
|
571
|
-
return { ...defaultFont }
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* 获取默认字体名称
|
|
576
|
-
*/
|
|
577
|
-
function getDefaultFontFamily() {
|
|
578
|
-
return defaultFont.name
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* 获取字体 fallback 链
|
|
583
|
-
* @napi-rs/canvas 支持逗号分隔的字体链
|
|
584
|
-
*/
|
|
585
|
-
function getFontFallbackChain(primaryFont, text = '') {
|
|
586
|
-
const chain = []
|
|
587
|
-
const validated = validateFont(primaryFont || defaultFont.name)
|
|
588
|
-
|
|
589
|
-
// 主字体
|
|
590
|
-
if (validated && validated !== 'sans-serif') {
|
|
591
|
-
chain.push(validated)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// 检查文本是否包含中文或 emoji
|
|
595
|
-
const hasChinese = /[\u4e00-\u9fff]/.test(text || primaryFont || '')
|
|
596
|
-
const hasEmoji = /[\u{1F300}-\u{1F9FF}]/u.test(text || '')
|
|
597
|
-
|
|
598
|
-
// 中文字体优先级
|
|
599
|
-
if (hasChinese) {
|
|
600
|
-
const chineseFonts = ['Microsoft YaHei', 'PingFang SC', 'SimHei', 'Noto Sans CJK SC', 'SimSun']
|
|
601
|
-
for (const cf of chineseFonts) {
|
|
602
|
-
if (!chain.includes(cf) && registeredFonts.has(cf)) {
|
|
603
|
-
chain.push(cf)
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Emoji 字体优先级
|
|
609
|
-
if (hasEmoji) {
|
|
610
|
-
const emojiFontList = ['Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', 'Noto Emoji', 'Symbola']
|
|
611
|
-
for (const ef of emojiFontList) {
|
|
612
|
-
if (!chain.includes(ef) && registeredFonts.has(ef)) {
|
|
613
|
-
chain.unshift(ef) // emoji 字体放最前面
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// 添加默认字体(如果不在链中)
|
|
619
|
-
if (!chain.includes(defaultFont.name)) {
|
|
620
|
-
chain.push(defaultFont.name)
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// 添加通用字体族作为最终回退
|
|
624
|
-
const genericFallbacks = ['sans-serif', 'serif', 'monospace']
|
|
625
|
-
for (const fb of genericFallbacks) {
|
|
626
|
-
if (!chain.includes(fb) && registeredFonts.has(fb)) {
|
|
627
|
-
chain.push(fb)
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
return chain
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* 使用 @napi-rs/canvas 匹配字体(用于文本度量)
|
|
636
|
-
*/
|
|
637
|
-
function matchFont(text, fontFamily) {
|
|
638
|
-
if (!GlobalFonts) return fontFamily || defaultFont.name
|
|
639
|
-
|
|
640
|
-
try {
|
|
641
|
-
// GlobalFonts.matchFont 可以在没有精确字体时返回最接近的匹配
|
|
642
|
-
const matched = GlobalFonts.matchFont(text, fontFamily)
|
|
643
|
-
return matched || fontFamily || defaultFont.name
|
|
644
|
-
} catch (e) {
|
|
645
|
-
return fontFamily || defaultFont.name
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* 获取所有已注册的字体家族
|
|
651
|
-
*/
|
|
652
|
-
function getFontFamilies() {
|
|
653
|
-
if (GlobalFonts && GlobalFonts.families) {
|
|
654
|
-
return GlobalFonts.families
|
|
655
|
-
}
|
|
656
|
-
return Array.from(registeredFonts.keys())
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// 初始化
|
|
660
|
-
initFonts()
|
|
661
|
-
|
|
662
|
-
module.exports = {
|
|
663
|
-
init: initFonts,
|
|
664
|
-
registerFontFile,
|
|
665
|
-
validateFont,
|
|
666
|
-
listAllFonts,
|
|
667
|
-
getDefaultFont,
|
|
668
|
-
getDefaultFontFamily,
|
|
669
|
-
getFontFallbackChain,
|
|
670
|
-
getFontFamilies,
|
|
671
|
-
matchFont,
|
|
672
|
-
isEmojiFont,
|
|
673
|
-
getRegisteredFonts: () => Array.from(registeredFonts.keys()),
|
|
674
|
-
}
|