243 lines
6.4 KiB
Markdown
243 lines
6.4 KiB
Markdown
# DETAIL-DESIGN
|
||
|
||
更新时间:2026-02-07
|
||
|
||
## 1. 文档目标
|
||
|
||
本文定义 `font2svg` 的详细设计,覆盖以下范围:
|
||
|
||
- Web 前端(`frontend/`)的模块职责、数据结构、处理流程
|
||
- Python CLI(`font2svg.py` / `pic2svg.py`)的职责边界
|
||
- 字体资源组织规范与导出策略
|
||
- 性能、错误处理、可维护性约束
|
||
|
||
## 2. 系统边界
|
||
|
||
## 2.1 子系统划分
|
||
|
||
1. `frontend/`:交互式预览与导出(主用户入口)
|
||
2. `scripts/generate-font-list.py`:字体清单构建
|
||
3. `font2svg.py`:命令行字体文本转 SVG
|
||
4. `pic2svg.py`:命令行图片转 SVG
|
||
|
||
## 2.2 非目标
|
||
|
||
- 不提供后端 API
|
||
- 不做字体版权管理系统
|
||
- 不做云端存储与用户账号
|
||
|
||
## 3. 前端架构设计
|
||
|
||
## 3.1 技术选型
|
||
|
||
- 框架:Vue 3 + Composition API
|
||
- 构建:Vite + `vite-plugin-wasm`
|
||
- 状态:Pinia
|
||
- 样式:UnoCSS + 手写样式
|
||
- 字体解析:`opentype.js`
|
||
- 字形 shaping:`harfbuzzjs`(已封装,主链路暂未强依赖)
|
||
|
||
## 3.2 目录与模块映射
|
||
|
||
- `src/App.vue`:页面总编排、交互入口、导出触发
|
||
- `src/stores/fontStore.ts`:字体域状态
|
||
- `src/stores/uiStore.ts`:UI 域状态与导出选择
|
||
- `src/components/FontSelector.vue`:字体搜索与树渲染入口
|
||
- `src/components/FontTree.vue`:分类树、收藏、预览勾选
|
||
- `src/components/FavoritesList.vue`:收藏列表
|
||
- `src/components/SvgPreview.vue`:预览生成调度
|
||
- `src/utils/svg-builder.ts`:SVG 生成核心
|
||
- `src/utils/download.ts`:下载与打包
|
||
- `src/utils/font-loader.ts`:字体加载(含进度)
|
||
- `src/utils/text-layout.ts`:文本换行标准化
|
||
|
||
## 3.3 状态模型
|
||
|
||
## FontInfo
|
||
|
||
```ts
|
||
interface FontInfo {
|
||
id: string
|
||
name: string
|
||
path: string
|
||
category: string
|
||
isFavorite: boolean
|
||
font?: Font
|
||
loaded: boolean
|
||
progress: number
|
||
}
|
||
```
|
||
|
||
## UI 持久化 Key
|
||
|
||
- `font.favoriteFontIds`
|
||
- `font.previewFontIds`
|
||
- `font.expandedCategories`
|
||
- `ui.fontSize`
|
||
- `ui.inputText`
|
||
- `ui.textColor`
|
||
- `ui.selectedExportItems`
|
||
|
||
## 4. 关键流程设计
|
||
|
||
## 4.1 字体清单加载
|
||
|
||
1. `useFontLoader()` 在 `App.vue` 初始化阶段触发。
|
||
2. 请求 `/fonts.json`。
|
||
3. 每条记录映射为 `FontInfo`,加入 `fontStore.fonts`。
|
||
4. 调用 `updateFontTree()` 生成分组树。
|
||
|
||
异常策略:请求失败弹窗提示,并记录控制台错误。
|
||
|
||
## 4.2 字体按需加载
|
||
|
||
入口:`fontStore.loadFont(fontInfo)`
|
||
|
||
- 若 `loaded=true` 或无路径则直接返回
|
||
- 同字体并发请求通过 `loadingFontTasks` 去重
|
||
- 使用 `loadFontWithProgress` 拉取字体并实时写入 `progress`
|
||
- 解析成功后写入 `fontInfo.font` 与 `loaded=true`
|
||
|
||
设计意图:避免初始一次性加载全部字体导致内存抖动。
|
||
|
||
## 4.3 预览生成调度
|
||
|
||
入口:`SvgPreview.vue`
|
||
|
||
1. 监听 `previewFonts/inputText/fontSize/fillColor` 变化。
|
||
2. 采用 `240ms` 防抖触发重算。
|
||
3. 每批最多处理 `20` 个字体,批内并发 `4`。
|
||
4. 借助 `IntersectionObserver` 懒加载后续批次。
|
||
5. 使用 `previewGeometryCache` 缓存几何结果,颜色切换直接替换 token。
|
||
|
||
核心常量:
|
||
|
||
- `PREVIEW_DEBOUNCE_MS = 240`
|
||
- `PREVIEW_BATCH_SIZE = 20`
|
||
- `PREVIEW_CONCURRENCY = 4`
|
||
- `PREVIEW_GEOMETRY_CACHE_LIMIT = 600`
|
||
|
||
取消策略:用 `previewGenerationToken` 判定过期任务,避免旧结果污染。
|
||
|
||
## 4.4 文本布局
|
||
|
||
`wrapTextByChars(text, 45)` 策略:
|
||
|
||
- 统一换行符 `\r\n/\r -> \n`
|
||
- 保留手动换行
|
||
- 每行按字符数上限切分(默认 45)
|
||
|
||
说明:该策略简单稳定,但不进行词边界或 East Asian 宽度感知换行。
|
||
|
||
## 4.5 SVG 生成
|
||
|
||
当前主链路:`generateSvg(options)`
|
||
|
||
- 基于 `opentype.js` 的 `charToGlyph` + path 指令拼装
|
||
- 计算 glyph 边界盒,汇总 viewBox 与 width/height
|
||
- 输出 `<g transform="translate(... ) scale(1 -1)">` 的坐标翻转结构
|
||
|
||
高级链路:`generateSvgWithHarfbuzz(options, fontBuffer)`
|
||
|
||
- 已封装 HarfBuzz shaping 与定位缩放逻辑
|
||
- 目前未接到主预览流程
|
||
|
||
## 4.6 导出流程
|
||
|
||
入口:`App.vue -> handleExport('svg' | 'png')`
|
||
|
||
1. 先清理失效导出项(不在当前预览集合中的项)
|
||
2. 单项导出:直接下载
|
||
3. 多项导出:聚合后 ZIP 下载
|
||
4. PNG 导出:先 SVG,再 `canvas` 渲染为 Blob
|
||
|
||
命名规则:
|
||
|
||
- SVG:`{fontPart}_{textPart}.svg`
|
||
- PNG:`{fontPart}_{textPart}.png`
|
||
- `textPart` 截取前 8 字符
|
||
- 非法字符统一替换 `_`
|
||
|
||
## 5. 字体资源规范
|
||
|
||
## 5.1 目录约束
|
||
|
||
字体唯一来源:`frontend/public/fonts/`
|
||
|
||
- 支持多级目录
|
||
- 分类名来自相对目录路径
|
||
- 根目录字体分类记为 `未分类`
|
||
|
||
## 5.2 fonts.json 结构
|
||
|
||
由 `scripts/generate-font-list.py` 生成:
|
||
|
||
```json
|
||
{
|
||
"id": "分类/字体名",
|
||
"name": "字体名",
|
||
"filename": "字体文件名.ttf",
|
||
"category": "分类",
|
||
"path": "/fonts/分类/字体文件名.ttf"
|
||
}
|
||
```
|
||
|
||
## 6. Python CLI 设计
|
||
|
||
## 6.1 font2svg.py
|
||
|
||
职责:把给定字体与文本转换为 SVG。
|
||
|
||
- 使用 `uharfbuzz` 做 shaping
|
||
- 使用 `fonttools` 读取 glyph 与路径
|
||
- 支持单字体或目录批量转换
|
||
- 支持字距参数 `--letter-spacing`
|
||
|
||
## 6.2 pic2svg.py
|
||
|
||
职责:将二值化后的图片轮廓矢量化。
|
||
|
||
- OpenCV 预处理灰度与阈值
|
||
- 可选圆拟合快速路径
|
||
- 默认依赖 potrace 做高保真描边
|
||
|
||
## 7. 性能设计
|
||
|
||
- 字体元数据与字体文件解耦(先列表后按需)
|
||
- 预览批处理 + 并发上限
|
||
- 预览项懒加载
|
||
- 几何缓存与颜色 token 替换,减少重复 path 生成
|
||
|
||
## 8. 错误处理设计
|
||
|
||
- 用户可恢复错误:`alert` 提示(如空文本、未选导出项)
|
||
- 任务级错误:控制台 `console.error/warn`,不中断整个批次
|
||
- 资源加载失败:单字体失败不阻断其他字体渲染
|
||
|
||
## 9. 测试与质量门禁
|
||
|
||
当前仓库实际情况:
|
||
|
||
- 有 TypeScript 编译检查(`pnpm -C frontend run build` 中包含 `vue-tsc`)
|
||
- 暂无标准 `lint` 脚本
|
||
- 暂无标准单元测试脚本
|
||
|
||
建议目标:
|
||
|
||
1. 增加 `lint`(ESLint)
|
||
2. 增加 `test`(Vitest)
|
||
3. 在 CI 中强制执行 `lint + typecheck + test`
|
||
|
||
## 10. 已知限制
|
||
|
||
- 主预览链路尚未默认启用 HarfBuzz shaping
|
||
- 自动换行仅按固定字符数,不考虑语义断句
|
||
- 浏览器内批量 PNG 导出在极大尺寸时可能触发内存压力
|
||
|
||
## 11. 后续演进建议
|
||
|
||
1. 将 `generateSvgWithHarfbuzz` 接入主链路并提供回退机制
|
||
2. 为预览与导出建立一致性回归样例
|
||
3. 增加导出并发队列与失败重试(有限次数)
|
||
4. 统一日志级别,移除生产环境调试日志
|