6.4 KiB
6.4 KiB
DETAIL-DESIGN
更新时间:2026-02-07
1. 文档目标
本文定义 font2svg 的详细设计,覆盖以下范围:
- Web 前端(
frontend/)的模块职责、数据结构、处理流程 - Python CLI(
font2svg.py/pic2svg.py)的职责边界 - 字体资源组织规范与导出策略
- 性能、错误处理、可维护性约束
2. 系统边界
2.1 子系统划分
frontend/:交互式预览与导出(主用户入口)scripts/generate-font-list.py:字体清单构建font2svg.py:命令行字体文本转 SVGpic2svg.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
interface FontInfo {
id: string
name: string
path: string
category: string
isFavorite: boolean
font?: Font
loaded: boolean
progress: number
}
UI 持久化 Key
font.favoriteFontIdsfont.previewFontIdsfont.expandedCategoriesui.fontSizeui.inputTextui.textColorui.selectedExportItems
4. 关键流程设计
4.1 字体清单加载
useFontLoader()在App.vue初始化阶段触发。- 请求
/fonts.json。 - 每条记录映射为
FontInfo,加入fontStore.fonts。 - 调用
updateFontTree()生成分组树。
异常策略:请求失败弹窗提示,并记录控制台错误。
4.2 字体按需加载
入口:fontStore.loadFont(fontInfo)
- 若
loaded=true或无路径则直接返回 - 同字体并发请求通过
loadingFontTasks去重 - 使用
loadFontWithProgress拉取字体并实时写入progress - 解析成功后写入
fontInfo.font与loaded=true
设计意图:避免初始一次性加载全部字体导致内存抖动。
4.3 预览生成调度
入口:SvgPreview.vue
- 监听
previewFonts/inputText/fontSize/fillColor变化。 - 采用
240ms防抖触发重算。 - 每批最多处理
20个字体,批内并发4。 - 借助
IntersectionObserver懒加载后续批次。 - 使用
previewGeometryCache缓存几何结果,颜色切换直接替换 token。
核心常量:
PREVIEW_DEBOUNCE_MS = 240PREVIEW_BATCH_SIZE = 20PREVIEW_CONCURRENCY = 4PREVIEW_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')
- 先清理失效导出项(不在当前预览集合中的项)
- 单项导出:直接下载
- 多项导出:聚合后 ZIP 下载
- 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 生成:
{
"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脚本 - 暂无标准单元测试脚本
建议目标:
- 增加
lint(ESLint) - 增加
test(Vitest) - 在 CI 中强制执行
lint + typecheck + test
10. 已知限制
- 主预览链路尚未默认启用 HarfBuzz shaping
- 自动换行仅按固定字符数,不考虑语义断句
- 浏览器内批量 PNG 导出在极大尺寸时可能触发内存压力
11. 后续演进建议
- 将
generateSvgWithHarfbuzz接入主链路并提供回退机制 - 为预览与导出建立一致性回归样例
- 增加导出并发队列与失败重试(有限次数)
- 统一日志级别,移除生产环境调试日志