# 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 - 输出 `` 的坐标翻转结构 高级链路:`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. 统一日志级别,移除生产环境调试日志