Files
font2pic/PLAN.md
2026-02-10 13:47:16 +08:00

1239 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 项目计划2026-02-08
## 0. 手动切换路由方案2026-02-10
### 0.1 目标
- 支持在**不发布小程序新版本**的情况下,从服务器 A`fonts.biboer.cn`)切换到服务器 B`mac.biboer.cn`),或反向切换。
- 切换仅通过手动修改远端配置文件触发,不引入自动健康探测。
- 避免 A/B 来回抖动切换。
### 0.2 已确认约束
- 字体目录统一一份:`/fonts/`A、B 各自托管同结构字体文件)。
- 配置文件独立管理:
- Web`/fonts.json`(可选 `/default.json`
- 小程序:`/miniprogram/assets/fonts.json``/miniprogram/assets/default.json`
- 小程序合法域名已包含 A/B 两个域名。
- 不使用 `version` 字段。
### 0.3 路由配置文件route-config.json
- 建议路径:`/miniprogram/assets/route-config.json`A、B 都提供)
- 建议结构:
```json
{
"active": "A",
"cooldownMinutes": 10,
"servers": {
"A": { "baseUrl": "https://fonts.biboer.cn" },
"B": { "baseUrl": "https://mac.biboer.cn" }
}
}
```
字段说明:
- `active`: 当前希望启用的目标服务器(`A``B`)。
- `cooldownMinutes`: 最短驻留时间。
- `10` 表示 10 分钟内不允许再次切换。
- `0` 表示可立即切换。
### 0.4 双确认切换规则(核心)
当客户端当前连接在 A且读取到 A 配置 `active=B` 时:
1. 客户端继续请求 B 的 `route-config.json`
2. 只有 B 也返回 `active=B`,才允许切换到 B。
3. 若 A/B 不一致,则保持当前服务器不变。
4. 若读取 B 失败(超时、非 200、JSON 非法),保持当前服务器不变,并记录失败日志。
反向切换B -> A同理执行。
### 0.5 A/B 交互与同步机制
为满足“双确认”A/B 需要配置一致性机制(交互):
- 维护端本地保留单一配置源文件Git 仓库内)。
- 通过 `deploy.sh` 一次性下发到 A、B。
- 采用“临时文件 + 原子替换mv”发布避免客户端读取半文件。
推荐切换步骤A -> B
1. 同步配置到 B确保 B 返回 `active=B`
2. 再同步配置到 A改为 `active=B`
3. 客户端双确认通过后完成切换。
### 0.6 客户端状态与防抖
本地持久化字段:
- `activeServerKey`(当前服务器)
- `lastSwitchAt`(最后切换时间戳)
- `routeConfigCache`(最近一次配置)
- `lastRouteCheckAt`(最后一次读取 route-config 时间戳)
切换判定:
-`cooldownMinutes > 0``now - lastSwitchAt < cooldownMinutes * 60 * 1000`,拒绝切换。
-`cooldownMinutes = 0`,通过双确认即可立即切换。
### 0.6.1 route-config.json 读取时机
1. 启动读取P0
- 小程序冷启动时优先读取 `route-config.json`,再加载 `fonts.json/default.json` 与 API 请求。
2. 回前台读取P0
- `App.onShow` 触发时检查是否超过最小间隔(例如 60 秒),超过则读取。
3. 失败兜底读取P0
- 当 API 或配置拉取连续失败时,立即触发一次读取并执行双确认逻辑。
- 若目标服务器配置读取失败,则仅记录日志并维持当前服务器,不执行切换。
4. 手动刷新读取P1可选
- 提供调试入口(如“刷新配置”按钮)用于即时验证切换。
节流规则:
-`now - lastRouteCheckAt < 60s`,跳过非必要读取,避免频繁请求。
### 0.7 实施任务拆分
1. 小程序端
- 新增 `route-manager`(加载路由配置、执行双确认、应用 cooldown、持久化状态
- `render-api``font-loader` 改为读取 `route-manager` 当前 `baseUrl`
- 启动时先加载路由,再加载 `fonts.json/default.json`
2. 服务端与部署
- A/B 都部署 `route-config.json`
- 使用 `deploy.sh` 统一发布:一次性下发到两台服务器并做原子替换。
- `deploy.sh` 负责同步 `route-config.json`、字体配置文件,并执行可选巡检。
- 增加巡检命令:检查 A/B 当前 `active` 是否一致。
3. 文档
- 更新 `miniprogram/README.md`:增加无发版切换流程。
- 增加运维操作手册A->B、B->A、回滚流程。
### 0.8 验收标准
- 仅修改远端 `route-config.json`,小程序无需发版即可切换 A/B。
- `cooldownMinutes=10`10 分钟内不会再次切换。
- `cooldownMinutes=0` 时,双确认满足后可立即切换。
- A/B 配置不一致时不发生切换。
- 目标服务器配置读取失败(超时/非 200/JSON 非法)时不发生切换。
- 切换后 API、字体清单、默认配置均来自目标服务器。
### 0.9 风险与回滚
- 风险A/B 配置不同步导致无法切换(预期保护行为)。
- 风险CDN 缓存导致短时间读取旧配置(通过短缓存 + 原子发布缓解)。
- 回滚:将 A/B 两端 `active` 同步改回原服务器,并设置 `cooldownMinutes=0` 可快速恢复。
## 1. 当前状态
### 1.1 已完成(保留能力)
- Web 应用主链路可用字体选择、预览、收藏、导出SVG/PNG
- Python CLI 可独立运行:`font2svg.py``pic2svg.py`
- 字体清单生成脚本可用:`scripts/generate-font-list.py`
### 1.2 新增目标
- 在不影响现有 Web/CLI 的前提下,新增微信小程序版本(新目录:`miniprogram/`
- 小程序版本以移动端体验为主,不做 Web 页面 1:1 迁移
## 2. 小程序专项范围
### 2.1 技术选型
**开发方式**微信小程序原生开发TypeScript + WXML + WXSS
- 理由:充分利用 Worker、Canvas 2D 等原生能力,避免框架转换损耗
- 放弃 Taro/uni-app复杂业务逻辑转换可能引入额外兼容问题
**核心算法复用**:直接复制 Web 端 `utils/` 核心模块
- `svg-builder.ts`:字形路径生成与 SVG 构建(纯函数,无浏览器 API 依赖)
- `text-layout.ts`:文本换行逻辑
- `font-loader.ts`:适配为小程序 API`wx.downloadFile` + `wx.getFileSystemManager`
**字体资源托管**:云存储(微信云开发 / 第三方 OSS
- 字体文件上传到云端,生成 CDN URL
- `fonts.json` 清单文件托管(含字体元数据)
- 按需下载,突破小程序 2MB 主包限制
### 2.2 范围内MVP
- 文本输入 + 字体选择(搜索/分类/收藏)+ 实时预览
- 字号、颜色调节
- 导出 SVG文件系统写入 + 分享)
- 导出 PNGCanvas 2D 渲染 + 保存相册)
- 本地状态持久化(文本、字体、参数)
### 2.3 范围外(后续迭代)
- HarfBuzz/WASM 高级 shaping首版关闭使用 opentype.js 基础能力)
- 多字体对比预览Web 版特性,小程序屏幕受限)
- 批量导出与 ZIP 打包(小程序 API 限制)
- 字体树分类折叠展开(简化为扁平列表 + 搜索)
## 3. 移动端差异与技术策略
### 3.1 交互策略
- 单列流程:输入 -> 预览 -> 导出
- 大按钮、触控优先、避免多栏复杂布局
### 3.2 性能策略
- 字形生成放入 `Worker`,主线程专注渲染
- 长文本分批处理,支持任务取消
- 字体对象缓存与内存上限控制
### 3.3 平台适配策略
- 文件系统:`wx.getFileSystemManager()` + `writeFile/saveFile`
- PNG 导出:`wx.canvasToTempFilePath`
- 文件分享:`wx.shareFileMessage`
- 图片保存:`wx.saveImageToPhotosAlbum`(带授权处理)
- 预览:优先 `image` 组件渲染 SVG必要时回退 PNG
### 3.4 包体策略
- 字体与 Worker 支持分包/按需加载
- 首包仅保留必要资源
## 4. 目录结构设计
```
miniprogram/
├── pages/ # 页面
│ ├── index/ # 主页(输入+预览+导出)
│ │ ├── index.wxml
│ │ ├── index.ts
│ │ ├── index.wxss
│ │ └── index.json
│ └── font-picker/ # 字体选择页
│ ├── font-picker.wxml
│ ├── font-picker.ts
│ ├── font-picker.wxss
│ └── font-picker.json
├── components/ # 组件
│ ├── text-input/ # 文本输入组件
│ ├── font-item/ # 字体列表项
│ ├── preview-card/ # 预览卡片
│ └── export-panel/ # 导出面板
├── workers/ # Worker 线程
│ └── svg-generator/ # SVG 生成 Worker
│ └── index.ts
├── utils/ # 工具函数
│ ├── core/ # 核心算法(复用 Web
│ │ ├── svg-builder.ts # SVG 生成核心
│ │ ├── text-layout.ts # 文本换行
│ │ └── glyph-path.ts # 字形路径
│ ├── mp/ # 小程序专用
│ │ ├── font-loader-mp.ts # 字体加载适配
│ │ ├── storage-mp.ts # 存储适配
│ │ ├── canvas-export.ts # Canvas 导出
│ │ └── share-mp.ts # 分享/保存
│ └── worker-manager.ts # Worker 管理器
├── typings/ # 类型定义(复用 Web types/
│ └── font.d.ts
├── assets/ # 静态资源
│ └── icons/ # 图标(复用 Web
├── styles/ # 全局样式
│ └── variables.wxss # 设计 token基于 Figma
├── miniprogram_npm/ # npm 构建产物
├── app.ts # 小程序入口
├── app.json # 全局配置
├── app.wxss # 全局样式
├── package.json # npm 依赖
├── tsconfig.json # TypeScript 配置
└── project.config.json # 微信开发者工具配置
```
## 5. 里程碑与验收
### M1工程骨架P0
**实施步骤**
1. 创建 `miniprogram/` 目录,微信开发者工具初始化原生项目
2. 配置 `project.config.json`
- 设置 `miniprogramRoot`、基础库版本 ≥ 2.9.0
- 启用 TypeScript 编译插件
3. 创建 `package.json`,安装依赖:
```json
{
"dependencies": {
"opentype.js": "^1.3.4"
},
"devDependencies": {
"miniprogram-api-typings": "latest",
"typescript": "~5.9.3"
}
}
```
4. 配置 `tsconfig.json`(参考 `frontend/tsconfig.json`
5. 创建 `app.ts`、`app.json`、`app.wxss`
6. 创建首页 `pages/index/`(空白页面)
7. 微信开发者工具"构建 npm"
**验收标准**
- 开发者工具可启动并进入首页(显示空白页面)
- TypeScript 编译无错误
- npm 包构建成功(`miniprogram_npm/` 生成)
### M2核心算法迁移P0
**实施步骤**
1. **复制纯算法模块**
- `frontend/src/utils/svg-builder.ts` → `miniprogram/utils/core/svg-builder.ts`
- 保留 `generateSvg()` 函数(移除 `generateSvgWithHarfbuzz()`
- `frontend/src/utils/text-layout.ts` → `miniprogram/utils/core/text-layout.ts`
- 提取字形路径逻辑到 `miniprogram/utils/core/glyph-path.ts`
2. **复制类型定义**
- `frontend/src/types/font.d.ts` → `miniprogram/typings/font.d.ts`
3. **平台适配层开发**
- `miniprogram/utils/mp/font-loader-mp.ts`
- `loadFontFromUrl()`:使用 `wx.downloadFile()` + `wx.getFileSystemManager()`
- `miniprogram/utils/mp/storage-mp.ts`
- 封装 `wx.getStorageSync/setStorageSync`
- 提供类型安全的 `getStorage<T>`、`setStorage<T>` 方法
4. **编写单元测试**
- `miniprogram/utils/core/__tests__/svg-builder.test.ts`
- 测试用例:单字符、多字符、空字符串、特殊字符
**验收标准**
- 输入文本 + Font 对象可生成有效 SVG 字符串
- SVG 结构正确(包含 `<svg>`、`<path>`、`viewBox`
- 核心算法单测通过(覆盖率 > 80%
### M3预览链路P0
**实施步骤**
1. **主页布局**`pages/index/`
- 文本输入区(`<textarea>`
- 字体选择器(跳转到 `font-picker` 页面)
- 参数调节(字号 `<slider>`、颜色 `<picker>`
- 预览区(`<image>` 或 `<canvas>`
- 导出按钮(底部固定)
2. **字体选择页**`pages/font-picker/`
- 搜索框(`<input>` 实时过滤)
- 字体列表(`<scroll-view>` + 虚拟列表优化)
- 从云端加载 `fonts.json`
3. **预览生成逻辑**
- 用户选择字体 → 触发 `loadFontFromUrl()`
- 调用 `generateSvg()` 生成 SVG 字符串
- 渲染方案:`<image src="data:image/svg+xml,{{encodedSvg}}">`
4. **状态管理**
- 使用 `storage-mp` 持久化:`inputText`、`fontSize`、`textColor`、`selectedFontId`
- 页面 `onLoad()` 恢复状态
**验收标准**
- 真机可稳定预览iOS + Android 各 1 款机型)
- 文本修改后预览更新延迟 < 500ms
- 切换字体后预览正确渲染
- 调整字号/颜色后预览实时刷新
### M4导出与分享P0
**实施步骤**
1. **SVG 导出**`utils/mp/share-mp.ts`
- `saveSvgToLocal(svgString, filename)`
- 使用 `wx.getFileSystemManager().writeFile()` 写入用户目录
- 返回文件临时路径
- `shareFileToChat(filePath)`
- 调用 `wx.shareFileMessage()` 分享到微信聊天
2. **PNG 导出**`utils/mp/canvas-export.ts`
- `svgToPng(svgString, width, height)`
- 创建离屏 Canvas`wx.createOffscreenCanvas({ type: '2d' })`
- SVG → Image → Canvas 渲染
- 使用 `canvas.toDataURL()` 或 `wx.canvasToTempFilePath()` 导出
- `savePngToPhotos(pngPath)`
- 调用 `wx.saveImageToPhotosAlbum()`
- 处理授权拒绝(引导用户手动开启)
3. **错误处理**
- 文件写入失败提示
- 授权拒绝引导(显示设置路径)
- 分享取消提示
**验收标准**
- SVG 导出成功率 > 95%(真机测试 20 次)
- PNG 导出图片清晰度正常(无模糊、颜色偏差)
- 授权拒绝后提示明确,可引导用户设置
- 文件分享到微信聊天后可正常打开
### M5性能与稳定性P1
**实施步骤**
1. **Worker 化字形生成**
- 创建 `workers/svg-generator/index.ts`
- 主线程 → Worker 消息协议:
```typescript
type GenerateRequest = {
id: string
fontBuffer: ArrayBuffer
text: string
options: SvgGenerateOptions
}
```
- Worker 内加载 opentype.js调用 `generateSvg()`,返回结果
- 创建 `utils/worker-manager.ts` 管理 Worker 生命周期
2. **字体缓存策略**
- 全局缓存已解析的 Font 对象Map<fontId, Font>
- LRU 淘汰:最多缓存 20 个字体
- 页面 `onUnload` 释放 Worker 资源
3. **性能优化**
- 限制并发 Worker 任务数(最多 3 个)
- 超时任务自动取消5 秒)
- 长文本分段处理(每段 100 字)
4. **真机回归测试**
- iOSiPhone SE2、iPhone 12
- Android小米 10、华为 P40
- 测试场景:
- 500 字长文本预览
- 连续切换 10 个字体
- 连续导出 5 次 PNG
**验收标准**
- 中等机型(骁龙 750G长文本500 字)预览生成时间 < 3s
- 主线程无明显卡顿(帧率 > 50fps
- 连续操作 10 次无内存泄漏(内存增长 < 50MB
- 低端机iPhone SE2可降级正常使用
## 6. 代码复用策略
### 6.1 直接复用(无需修改)
| Web 文件 | 小程序文件 | 说明 |
|---------|-----------|------|
| `frontend/src/utils/svg-builder.ts` | `miniprogram/utils/core/svg-builder.ts` | SVG 生成核心(移除 HarfBuzz 部分) |
| `frontend/src/utils/text-layout.ts` | `miniprogram/utils/core/text-layout.ts` | 文本换行逻辑 |
| `frontend/src/types/font.d.ts` | `miniprogram/typings/font.d.ts` | 类型定义 |
| `frontend/src/assets/icons/*.svg` | `miniprogram/assets/icons/*.svg` | SVG 图标 |
### 6.2 适配复用(需修改 API
| Web 文件 | 小程序文件 | 修改点 |
|---------|-----------|--------|
| `frontend/src/utils/font-loader.ts` | `miniprogram/utils/mp/font-loader-mp.ts` | `fetch` → `wx.downloadFile()` + `wx.getFileSystemManager()` |
| `frontend/src/utils/download.ts` | `miniprogram/utils/mp/canvas-export.ts` | `Blob` → `wx.canvasToTempFilePath()`,移除 JSZip |
| `frontend/src/stores/fontStore.ts` | `pages/index/index.ts` 内联状态 | `localStorage` → `wx.storage`,简化 Pinia 为页面级状态 |
### 6.3 重新实现(平台差异大)
- `frontend/src/components/SvgPreview.vue` → `pages/index/index.wxml` 预览区
- 移除 `IntersectionObserver`,改用 `<scroll-view bindscrolltolower>`
- 移除分批渲染(单字体预览无需分批)
- `frontend/src/components/FontTree.vue` → `pages/font-picker/`
- 树形结构降级为扁平列表 + 搜索
- 移除分类折叠展开
## 7. 资源处理方案
### 7.1 字体资源托管
**方案Cloudflare CDN + 海外服务器(已选定)**
#### 7.1.1 架构设计
```
字体文件流转:
海外服务器fonts.biboer.cn
↓ 托管静态文件
Cloudflare CDN自动缓存
↓ 全球加速
微信小程序(按需下载)
```
**优势**
- ✅ **免备案**:海外服务器无需 ICP 备案
- ✅ **免费 CDN**Cloudflare 免费计划提供全球加速
- ✅ **自动 HTTPS**Cloudflare 提供免费 SSL 证书
- ✅ **高可用**:自动 DDoS 防护 + 故障转移
- ✅ **缓存优化**:字体文件自动缓存到全球节点
#### 7.1.2 服务器端配置
**Nginx 静态文件服务配置**
```nginx
# /etc/nginx/sites-available/font2svg
server {
listen 80;
server_name fonts.biboer.cn;
# Cloudflare 会自动处理 HTTPS这里只需 HTTP
# 字体文件根目录
root /var/www/font2svg;
# 字体文件访问
location /fonts/ {
# CORS 配置(允许小程序跨域)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
# 缓存控制30天
expires 30d;
add_header Cache-Control "public, immutable";
# 字体 MIME 类型
types {
font/ttf ttf;
font/otf otf;
font/woff woff;
font/woff2 woff2;
}
# 支持 OPTIONS 预检请求
if ($request_method = OPTIONS) {
return 204;
}
}
# fonts.json 配置
location = /fonts.json {
add_header Access-Control-Allow-Origin *;
add_header Content-Type application/json;
expires 1h; # 短缓存,方便更新
}
# 可选Web 应用代理
location / {
proxy_pass http://localhost:5173;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
**部署命令**
```bash
# 1. 创建目录
ssh user@fonts.biboer.cn "mkdir -p /var/www/font2svg/fonts"
# 2. 上传字体文件
scp -r frontend/public/fonts/* user@fonts.biboer.cn:/var/www/font2svg/fonts/
# 3. 上传 fonts.json
scp frontend/public/fonts.json user@fonts.biboer.cn:/var/www/font2svg/
# 4. 设置权限
ssh user@fonts.biboer.cn "chmod -R 755 /var/www/font2svg"
# 5. 重启 Nginx
ssh user@fonts.biboer.cn "sudo systemctl restart nginx"
# 6. 测试访问
curl -I https://fonts.biboer.cn/fonts.json
```
#### 7.1.3 Cloudflare 配置
**步骤 1添加网站到 Cloudflare**
```bash
# 1. 登录 Cloudflare Dashboard: https://dash.cloudflare.com/
# 2. 添加网站 → 输入 biboer.com
# 3. 选择 Free 计划
# 4. 更新 DNS 服务器(在域名注册商处配置)
```
**步骤 2DNS 记录配置**
```
类型: A
名称: fonts
内容: [你的服务器IP]
代理状态: 已代理(橙色云朵)✓
TTL: Auto
```
**步骤 3SSL/TLS 设置**
```
SSL/TLS → 概述
加密模式: 完全(严格)或 灵活
SSL/TLS → 边缘证书
- 始终使用 HTTPS: 开启 ✓
- 自动 HTTPS 重写: 开启 ✓
- 最低 TLS 版本: TLS 1.2
```
**步骤 4缓存规则优化字体加载速度**
```
规则 → 页面规则 → 创建页面规则
URL: fonts.biboer.cn/fonts/*
设置:
- 缓存级别: 缓存所有内容
- 边缘缓存 TTL: 1 个月
- 浏览器缓存 TTL: 1 个月
```
**步骤 5性能优化**
```
速度 → 优化
- Auto Minify: 无需开启(字体是二进制)
- Brotli: 开启 ✓
- HTTP/3: 开启 ✓
网络
- WebSockets: 开启 ✓
- gRPC: 开启 ✓
```
#### 7.1.4 更新 fonts.json
```json
[
{
"id": "其他字体/AlimamaDaoLiTi",
"name": "AlimamaDaoLiTi",
"category": "其他字体",
"path": "https://fonts.biboer.cn/fonts/其他字体/AlimamaDaoLiTi.ttf",
"size": 2345678
},
{
"id": "庞门正道/庞门正道粗书体",
"name": "庞门正道粗书体",
"category": "庞门正道",
"path": "https://fonts.biboer.cn/fonts/庞门正道/庞门正道粗书体.ttf",
"size": 3456789
}
]
```
#### 7.1.5 小程序后台配置
**微信公众平台配置步骤**
```
1. 登录: https://mp.weixin.qq.com/
2. 进入: 开发 → 开发管理 → 服务器域名
3. downloadFile 合法域名 → 添加:
https://fonts.biboer.cn
注意事项:
- 必须使用 HTTPSCloudflare 自动提供)
- 域名无需备案(海外服务器)
- 每月最多修改 5 次
- 配置后等待 5-10 分钟生效
```
#### 7.1.6 小程序端加载代码
```typescript
// miniprogram/utils/mp/font-loader-mp.ts
export async function loadFontFromUrl(
url: string,
onProgress?: (loaded: number, total: number) => void
): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
// 1. 下载字体文件(通过 Cloudflare CDN
const downloadTask = wx.downloadFile({
url: url, // https://fonts.biboer.cn/fonts/xxx.ttf
success: (res) => {
if (res.statusCode === 200) {
// 2. 读取为 ArrayBuffer
wx.getFileSystemManager().readFile({
filePath: res.tempFilePath,
success: (data) => {
resolve(data.data as ArrayBuffer)
},
fail: reject
})
} else {
reject(new Error(`HTTP ${res.statusCode}`))
}
},
fail: reject
})
// 3. 进度回调
if (onProgress) {
downloadTask.onProgressUpdate((res) => {
onProgress(res.totalBytesWritten, res.totalBytesExpectedToWrite)
})
}
})
}
// 示例:加载 fonts.json
export async function loadFontsList(): Promise<FontInfo[]> {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://fonts.biboer.cn/fonts.json',
method: 'GET',
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data as FontInfo[])
} else {
reject(new Error(`加载字体列表失败: ${res.statusCode}`))
}
},
fail: reject
})
})
}
```
### 7.2 图标资源处理
**方案选择**
- 小程序 `<image>` 组件支持 SVG基础库 ≥ 2.3.0
- 直接复制 `frontend/src/assets/icons/*.svg` → `miniprogram/assets/icons/`
- 按 Figma 设计稿调整尺寸(假设 48rpx
**路径映射**(创建 `miniprogram/utils/icons.ts`
```typescript
export const ICONS = {
SEARCH: '/assets/icons/search.svg',
FAVORITE: '/assets/icons/星程字体.svg',
EXPORT_SVG: '/assets/icons/export-svg.svg',
EXPORT_PNG: '/assets/icons/export-png.svg',
ARROW_RIGHT: '/assets/icons/expand.svg',
}
```
## 8. UI 设计规范(基于 Figma 实际设计稿)
### 8.1 设计稿概览
**整体尺寸**426 x 956 px微信小程序标准尺寸
**页面结构**(单页垂直布局):
1. **顶部工具栏** (410x48px) - Logo + 品牌 + 字号滑块 + 颜色选择
2. **输入和导出栏** (410x38.78px) - 文本输入框 + 导出按钮组
3. **预览区** (410x649.21px) - 可滚动的多字体预览卡片列表
4. **底部选择区** (410x180px) - 左:字体选择(树形) | 右:已收藏字体
**布局特点**
- 无 TabBar单页垂直布局
- 预览区占据主要高度68%),支持滚动
- 底部字体选择和收藏并排显示(各 200px 宽)
- 支持多字体同时预览对比
### 8.2 颜色方案(从 Figma 提取)
| 颜色名称 | HEX 值 | 用途 |
|---------|--------|------|
| 主色/primary-6 | `#9B6BC2` | 主按钮、滑块、选中状态 |
| 主色/primary-7 | `#8552A1` | 主色深色变体 |
| 主色/primary-1 | `#F3EDF7` | 主色浅色背景 |
| 填充/fill-1 | `#F7F8FA` | 输入框背景 |
| 填充/fill-3 | `#E5E6EB` | 滑块轨道、边框 |
| 填充/fill-4 | `#C9CDD4` | 分割线、次要边框 |
| 填充/white | `#FFFFFF` | 纯白背景 |
| 文字/text-1 | `#FFFFFF` | 白色文字 |
| 文字/text-2 | `#C9CDD4` | 禁用文字 |
| 文字/text-3 | `#86909C` | 次要文字(字体名称) |
| 文字/text-4 | `#4E5969` | 主要文字 |
### 8.3 尺寸规范(转换为 rpx
**字体大小**
- 标题28rpx (14px × 2)
- 字体分类名21rpx (10.5px × 2)
- 字体名称预览16rpx (8px × 2)
- 字体名称列表14rpx (7px × 2)
- 输入提示12.928rpx (6.464px × 2)
**组件尺寸**
- Logo/图标96rpx × 96rpx (48px × 2)
- 字号滑块高度:~49rpx (24.504px × 2)
- 滑块调节按钮24.504rpx × 24.504rpx
- 颜色选择器48rpx × 48rpx
- 输入框高度77.57rpx (38.785px × 2)
- 导出按钮SVG/PNG61.714rpx × 61.714rpx
- 复选框21rpx × 21rpx (10.5px × 2)
- 收藏星标图标21rpx × 19.516rpx
**间距**
- 组件外边距16rpx (8px × 2)
- 组件内边距6.464rpx (3.232px × 2)
- 卡片圆角12.928rpx (6.464px × 2)
- 列表项间距14rpx (7px × 2)
- 预览卡片间距6.4rpx (3.2px × 2)
### 8.4 详细组件规范
#### 8.4.1 顶部工具栏Frame 7
```
[Logo 96×96] [TextToSVG文字图] [A-减号 滑块轨道 A+加号] [颜色选择器 48×48]
```
- Logo圆角 24rpx紫色渐变背景径向渐变
- 品牌文字150px 宽图片资源
- 字号滑块:
- 轨道:背景 #E5E6EB高度 3.064rpx,圆角 20rpx
- 填充:背景 #9B6BC2
- 拖动按钮:白色圆形,阴影,尺寸 ~36rpx
- 减号/加号图标24.504rpx
- 颜色选择器:圆形色轮图标
- 间距:组件间 16rpx
#### 8.4.2 输入和导出栏Frame 8
```
[输入框flex-1] [导出图标 + SVG按钮 + PNG按钮]
```
- 输入框:
- 背景:#F7F8FA
- 圆角12.928rpx (6.464px × 2)
- 内边距6.464rpx
- 占位文字:灰色 #4E596912.928rpx
- 导出按钮组:
- 容器:白色背景,边框 #E5E6EB (1.286rpx),圆角 10.286rpx
- 内边距:上下 5.142rpx,左右 10.286rpx
- 图标间距11.572rpx
- 导出文字图标 + SVG 图标 + PNG 图标
#### 8.4.3 预览区Frame 11/12
```
[标题:效果预览 28rpx]
[字体卡片1]
├─ 折叠栏:[展开图标] 小米小松字体 [复选框]
└─ 预览内容:星程字体 SVG 渲染
[字体卡片2]
├─ 折叠栏:...
└─ 预览内容:...
[...]
```
- 外容器:
- 边框:#F7E0E0 (0.8rpx)
- 圆角12.8rpx
- 内边距6.4rpx
- 标题黑色28rpxPingFang SC Regular
- 字体卡片:
- 折叠栏高度24rpx
- 底部边框:#C9CDD4 (0.48rpx)
- 展开图标16rpx × 16rpx树形折叠图标
- 字体名称:灰色 #86909C16rpx
- 复选框16rpx × 16rpx选中时紫色填充
- 预览内容区:
- 背景:白色
- 高度:自适应内容(根据文本长度和字号)
- 内边距3.706rpx
- SVG 预览图片自动居中显示
- 卡片间距6.4rpx
#### 8.4.4 底部选择区Frame 2130
**左侧:字体选择** (400rpx 宽)
```
[标题:字体选择 28rpx]
├── 小米字体(分类,可折叠)
│ ├─ [图标] 小米大宋字体 [复选框] [星标]
│ └─ [图标] 小米小松字体 [复选框] [星标✓]
├── 星程字体(分类,可折叠)
│ ├─ [图标] 星程中楷体 [复选框] [星标]
│ └─ [图标] 星程小标宋 [复选框] [星标]
```
- 容器:
- 边框:#F7E0E0 (1.166rpx)
- 圆角18.666rpx
- 内边距9.334rpx
- 分类标题:
- 字体PingFang SC Medium
- 大小21rpx (10.5px × 2)
- 颜色:黑色
- 树形缩进:
- 缩进线19.834rpx 宽
- 折叠/展开图标18.666rpx
- 字体项:
- 高度28rpx (14px × 2)
- 底部边框:#C9CDD4 (1.166rpx)
- 布局:[图标 18.666rpx] + [名称 flex-1 灰色 14rpx] + [复选框 21rpx] + [星标 21rpx]
- 间距9.334rpx
**右侧:已收藏字体** (400rpx 宽)
- 布局同左侧,但无分类结构
- 仅显示已收藏(星标已填充)的字体
- 标题:"已收藏字体"
### 8.5 交互规范(基于 Figma Annotations
**文本输入**
- 输入后回车或点击"预览"按钮生效
- 实时更新所有已勾选字体的预览
**字号调节**
- 拖动滑块或点击减号/加号按钮
- 预览窗口文字大小动态变化
**字体选择**
- 勾选复选框 → 添加到预览区顶部
- 取消勾选 → 从预览区移除
- 点击空心星标 → 收藏该字体(变为实心红色)
- 点击实心星标 → 取消收藏(变为空心)
**预览卡片**
- 点击折叠栏(字体名称区域)→ 展开/收起预览内容
- 点击预览内容区域 → 切换导出选中状态(复选框)
- 预览区支持滚动显示多个字体
**导出**
- 点击 SVG 按钮 → 导出预览区已勾选项的 SVG 文件
- 点击 PNG 按钮 → 导出预览区已勾选项的 PNG 图片
**字体树**
- 从 `fonts/` 目录读取所有字体
- 字体按目录树状分组
- 支持展开和收拢分类
- 支持单个选择或批量框选
### 8.6 设计 Token`miniprogram/styles/variables.wxss`
```css
/* 颜色 */
--color-primary: #9B6BC2;
--color-primary-dark: #8552A1;
--color-primary-light: #F3EDF7;
--color-bg: #F7F8FA;
--color-bg-white: #FFFFFF;
--color-border: #E5E6EB;
--color-border-light: #C9CDD4;
--color-border-error: #F7E0E0;
--color-text: #4E5969;
--color-text-secondary: #86909C;
--color-text-disabled: #C9CDD4;
--color-text-white: #FFFFFF;
--color-danger: #FF0D0D;
/* 字号 */
--font-size-title: 28rpx;
--font-size-subtitle: 21rpx;
--font-size-body: 16rpx;
--font-size-caption: 14rpx;
--font-size-tiny: 12rpx;
/* 间距 */
--space-xs: 6rpx;
--space-sm: 12rpx;
--space-md: 16rpx;
--space-lg: 28rpx;
/* 圆角 */
--radius-sm: 10rpx;
--radius-md: 12rpx;
--radius-lg: 24rpx;
/* 尺寸 */
--icon-size-xs: 16rpx;
--icon-size-sm: 18rpx;
--icon-size-md: 21rpx;
--icon-size-lg: 48rpx;
--icon-size-xl: 96rpx;
/* 边框 */
--border-width-thin: 0.8rpx;
--border-width-normal: 1.166rpx;
--border-width-thick: 1.286rpx;
```
```
## 9. 风险与降级
### 9.1 技术风险
| 风险 | 影响 | 降级方案 |
|------|------|---------|
| opentype.js 小程序兼容性问题 | 无法解析字体 | 改造 Python 脚本为云函数 API |
| 包体超限(> 2MB | 无法提交审核 | 启用分包加载,首包仅保留 3 个示例字体 |
| SVG 兼容差异(部分机型) | 预览显示异常 | 回退 PNG 预览模式Canvas 渲染) |
| Canvas 2D API 低端机性能差 | 导出 PNG 卡顿/失败 | 限制导出尺寸(最大 2048x2048|
| Worker 不稳定(部分安卓) | 主线程阻塞 | 降级为主线程同步计算+提示处理中 |
### 9.2 业务风险
| 风险 | 影响 | 降级方案 |
|------|------|---------|
| 字体版权问题 | 法律风险 | 仅保留 SIL OFL 开源字体 |
| 云存储费用超预算 | 成本增加 | 限制字体数量到 50 个+CDN 缓存 |
| 授权拒绝率高 | 保存相册功能受限 | 强化分享到聊天引导 |
| 审核不通过 | 无法上线 | 按审核意见调整,准备替代方案 |
### 9.3 降级触发条件
- **Worker 降级**Worker 创建失败 3 次或超时率 > 30%
- **PNG 导出降级**Canvas 渲染失败率 > 20% 或用户机型在黑名单
- **字体数量降级**:初始加载时间 > 3s 或内存占用 > 300MB
## 10. 执行原则
- **小步迭代**:每次只完成一个明确子目标(按里程碑推进)
- **变更聚焦**:仅修改当前里程碑所需文件,避免过度设计
- **验证先行**:每次改动后真机测试核心链路
- **文档同步**:关键变化同步更新 `README.md`/`USAGE.md`
- **性能意识**:每次 commit 前检查包体大小、真机帧率
## 11. 检查清单(上线前)
### 11.1 功能检查
- [ ] 字体列表加载成功(云存储/本地)
- [ ] 搜索字体名称正常
- [ ] 选择字体→预览实时更新
- [ ] 调整字号/颜色→预览正确
- [ ] 导出 SVG→文件写入→可分享
- [ ] 导出 PNG→保存相册成功
- [ ] 授权拒绝→提示引导设置
- [ ] 关闭重开→状态恢复
### 11.2 性能检查
- [ ] 首屏加载时间 < 2s
- [ ] 预览生成时间 < 1s50 字)
- [ ] 长文本500 字)< 3s
- [ ] 连续操作无内存泄漏
- [ ] 低端机iPhone SE2可正常使用
### 11.3 兼容性检查
- [ ] iOS 真机测试(≥ 2 款)
- [ ] Android 真机测试(≥ 2 款)
- [ ] 不同屏幕尺寸适配正常
- [ ] 横屏显示正常(或禁用)
### 11.4 合规检查
- [ ] 隐私政策包含云存储/相册权限说明
- [ ] 仅保留已获授权字体
- [ ] 无敏感词、广告、诱导分享
- [ ] 符合微信小程序运营规范
## 12. 官方能力依据(已核对)
- npm 支持:`https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html`
- Canvas 指南:`https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html`
- Canvas 导出:`https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.canvasToTempFilePath.html`
- 文件系统:`https://developers.weixin.qq.com/miniprogram/dev/api/file/FileSystemManager.html`
- 写文件:`https://developers.weixin.qq.com/miniprogram/dev/api/file/FileSystemManager.writeFile.html`
- 多线程 Worker`https://developers.weixin.qq.com/miniprogram/dev/framework/workers.html`
- WXWebAssembly`https://developers.weixin.qq.com/miniprogram/dev/framework/performance/wasm.html`
- image 组件(含 SVG`https://developers.weixin.qq.com/miniprogram/dev/component/image.html`
---
## 13. 下一步行动
### 13.1 立即执行(无依赖)
1. **M1 工程骨架搭建**
```bash
cd /Users/gavin/font2svg
# 使用微信开发者工具新建小程序项目
# 选择目录miniprogram/
# AppID测试号或已申请的小程序 AppID
# 模板:不使用模板(空项目)
# 语言TypeScript
```
2. **初始化配置文件**
- 创建 `miniprogram/package.json` 并安装依赖
- 配置 `miniprogram/tsconfig.json`
- 配置 `miniprogram/project.config.json`
### 13.2 设计稿已确认(可立即实施)
**Figma 设计稿关键信息已提取**
- ✅ 单页垂直布局426×956px
- ✅ 主色调:紫色 #9B6BC2品牌色 TextToSVG
- ✅ 完整颜色方案和尺寸规范
- ✅ 交互规范:支持多字体对比预览、树形字体选择、收藏功能
- ✅ 组件规范:滑块、复选框、预览卡片、字体树
**设计稿特色功能**
1. **品牌 Logo + 名称**:顶部固定,紫色渐变 Logo
2. **字号滑块**:实时调节预览大小(减号/滑块/加号)
3. **颜色选择器**:色轮图标,调节文本颜色
4. **多字体预览**:可同时预览多个字体效果
5. **字体树**:分类折叠展开,支持收藏星标
6. **导出按钮组**SVG + PNG 双格式导出
**下一步:可直接开始 M1 和 M3 实施**
### 13.3 并行推进(可同步)
在等待设计稿期间,可以同步进行:
- **M2 核心算法迁移**:复用 Web 端代码,平台 API 适配
- **字体资源云存储准备**
- 开通微信云开发环境
- 上传字体文件到云存储
- 生成 `fonts.json` 云端配置
### 13.4 优先级建议
1. **P0必须完成**M1 → M2 → M3 → M4
2. **P1性能优化**M5真机测试后评估是否需要 Worker
3. **P2体验增强**:收藏功能、搜索历史、多字体对比
---
## 附录:快速启动命令
### A. 开发环境启动
```bash
# 1. 安装微信开发者工具(如未安装)
# https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
# 2. 创建小程序项目目录
cd /Users/gavin/font2svg
mkdir -p miniprogram
# 3. 初始化 npm在微信开发者工具外执行
cd miniprogram
npm init -y
npm install opentype.js@1.3.4
npm install -D miniprogram-api-typings typescript@~5.9.3
# 4. 在微信开发者工具中打开 miniprogram/ 目录
# 工具 → 构建 npm
# 5. 开始开发
# 按里程碑 M1 → M2 → M3 → M4 → M5 顺序推进
```
### B. 字体资源部署Cloudflare + 海外服务器)
```bash
# 1. 在服务器创建目录
ssh user@fonts.biboer.cn "mkdir -p /var/www/font2svg/fonts"
# 2. 上传字体文件
cd /Users/gavin/font2svg
scp -r frontend/public/fonts/* user@fonts.biboer.cn:/var/www/font2svg/fonts/
scp frontend/public/fonts.json user@fonts.biboer.cn:/var/www/font2svg/
# 3. 配置 Nginx见 PLAN.md 7.1.2 节)
ssh user@fonts.biboer.cn
sudo nano /etc/nginx/sites-available/font2svg
# 粘贴配置内容,保存退出
sudo ln -s /etc/nginx/sites-available/font2svg /etc/nginx/sites-enabled/
sudo nginx -t # 测试配置
sudo systemctl restart nginx
# 4. 验证部署
curl -I https://fonts.biboer.cn/fonts.json
# 应返回 200 OK + Access-Control-Allow-Origin: *
# 5. 测试字体下载
curl -I "https://fonts.biboer.cn/fonts/其他字体/AlimamaDaoLiTi.ttf"
# 应返回 200 OK + Content-Type: font/ttf
```
### C. Cloudflare 配置验证
```bash
# 1. 检查 DNS 是否生效
nslookup fonts.biboer.cn
# 应返回 Cloudflare 的 IP非你的服务器 IP
# 2. 检查 SSL 证书
curl -vI https://fonts.biboer.cn/fonts.json 2>&1 | grep -i "SSL\|issuer"
# 应包含 Cloudflare SSL 证书信息
# 3. 检查缓存状态
curl -I https://fonts.biboer.cn/fonts.json | grep -i "cf-cache-status"
# HIT = 已缓存MISS = 未缓存首次访问DYNAMIC = 未缓存
# 4. 测试 CORS
curl -H "Origin: https://servicewechat.com" \
-H "Access-Control-Request-Method: GET" \
-X OPTIONS -I \
https://fonts.biboer.cn/fonts.json
# 应包含 Access-Control-Allow-Origin: *
```
### D. 小程序开发者工具调试
```javascript
// 1. 开启调试模式(跳过域名校验)
// 开发者工具 → 详情 → 本地设置
// ✓ 不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书
// 2. 在控制台测试字体加载
wx.request({
url: 'https://fonts.biboer.cn/fonts.json',
success: (res) => console.log('fonts.json 加载成功', res.data),
fail: (err) => console.error('fonts.json 加载失败', err)
})
wx.downloadFile({
url: 'https://fonts.biboer.cn/fonts/其他字体/AlimamaDaoLiTi.ttf',
success: (res) => {
console.log('字体下载成功', res.tempFilePath)
// 检查文件大小
wx.getFileInfo({
filePath: res.tempFilePath,
success: (info) => console.log('文件大小:', info.size, 'bytes')
})
},
fail: (err) => console.error('字体下载失败', err)
})
// 3. 正式发布前必须配置合法域名
// 微信公众平台 → 开发 → 开发管理 → 服务器域名
// downloadFile 合法域名: https://fonts.biboer.cn
```
### E. 常见问题排查
#### 问题 1字体下载失败 "不在合法域名列表中"
```
原因:未配置 downloadFile 合法域名或配置未生效
解决:
1. 检查小程序后台是否已添加 https://fonts.biboer.cn
2. 等待 5-10 分钟让配置生效
3. 开发阶段可暂时开启"不校验合法域名"
```
#### 问题 2CORS 错误
```
原因Nginx 未配置 CORS 头
解决:
1. 确认 Nginx 配置包含 Access-Control-Allow-Origin *
2. 重启 Nginx: sudo systemctl restart nginx
3. 清除 Cloudflare 缓存: Dashboard → 缓存 → 清除所有内容
```
#### 问题 3字体文件无法访问404
```
原因:文件路径不正确或权限问题
解决:
1. 检查文件是否存在:
ssh user@fonts.biboer.cn "ls -lh /var/www/font2svg/fonts/"
2. 检查权限:
ssh user@fonts.biboer.cn "chmod -R 755 /var/www/font2svg"
3. 检查 Nginx 配置的 root 路径是否正确
```
#### 问题 4Cloudflare 缓存过多导致更新不生效
```
解决:
1. Cloudflare Dashboard → 缓存 → 配置 → 清除所有内容
2. 或使用 URL 清除特定文件:
https://fonts.biboer.cn/fonts.json
3. 开发阶段可临时关闭缓存:
页面规则 → 缓存级别 → 绕过
```
#### 问题 5字体文件中文编码问题
```
原因URL 未编码中文字符
解决:
// 在小程序中使用 encodeURIComponent
const fontPath = 'https://fonts.biboer.cn/fonts/' +
encodeURIComponent('其他字体/AlimamaDaoLiTi.ttf')
wx.downloadFile({ url: fontPath })
```
---
**计划更新日期**2026-02-08
**下次更新**M1 完成后,根据实际情况调整后续里程碑细节
**部署方案**Cloudflare CDN + 海外服务器fonts.biboer.cn免备案