Files
font2pic/DETAIL-DESIGN.md
2026-02-07 16:05:38 +08:00

6.4 KiB
Raw Blame History

DETAIL-DESIGN

更新时间2026-02-07

1. 文档目标

本文定义 font2svg 的详细设计,覆盖以下范围:

  • Web 前端(frontend/)的模块职责、数据结构、处理流程
  • Python CLIfont2svg.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
  • 字形 shapingharfbuzzjs(已封装,主链路暂未强依赖)

3.2 目录与模块映射

  • src/App.vue:页面总编排、交互入口、导出触发
  • src/stores/fontStore.ts:字体域状态
  • src/stores/uiStore.tsUI 域状态与导出选择
  • src/components/FontSelector.vue:字体搜索与树渲染入口
  • src/components/FontTree.vue:分类树、收藏、预览勾选
  • src/components/FavoritesList.vue:收藏列表
  • src/components/SvgPreview.vue:预览生成调度
  • src/utils/svg-builder.tsSVG 生成核心
  • src/utils/download.ts:下载与打包
  • src/utils/font-loader.ts:字体加载(含进度)
  • src/utils/text-layout.ts:文本换行标准化

3.3 状态模型

FontInfo

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.fontloaded=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.jscharToGlyph + 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 导出:先 SVGcanvas 渲染为 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 生成:

{
  "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. 增加 lintESLint
  2. 增加 testVitest
  3. 在 CI 中强制执行 lint + typecheck + test

10. 已知限制

  • 主预览链路尚未默认启用 HarfBuzz shaping
  • 自动换行仅按固定字符数,不考虑语义断句
  • 浏览器内批量 PNG 导出在极大尺寸时可能触发内存压力

11. 后续演进建议

  1. generateSvgWithHarfbuzz 接入主链路并提供回退机制
  2. 为预览与导出建立一致性回归样例
  3. 增加导出并发队列与失败重试(有限次数)
  4. 统一日志级别,移除生产环境调试日志