Rework layout engine around exact-glyph collision, add tests and docs sync

Replace the old bbox/heuristic placement (scale search rounds, large-font
capping, stratified sampling, fill-retry ladders) with an area-model font
sizing pass feeding a C++ exact-glyph collision engine (centroid-biased
spiral + random probing, HD clearance refinement, density/hole
optimization). Simplify the frontend advanced-params panel and JobParams
type to match the surviving config surface, add a layout-constraints test
suite and a repeatable benchmark tool, and bring docs/*.md back in sync
with current code (plus new TESTING.md and DEPLOYMENT.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 18:32:25 +08:00
co-authored by Claude Sonnet 5
parent bf2b138007
commit 1d17b5e20d
24 changed files with 2600 additions and 1283 deletions
+143 -79
View File
@@ -1,45 +1,115 @@
# 项目标准说明
本文档按当前代码整理,覆盖项目边界、运行方式、输入输出和维护约定。最后核对代码时间:2026-06-09
本文档覆盖项目边界、目录结构、运行方式、输入输出和维护约定。核对代码时间:2026-07-25
## 项目目标
本项目生成基于名单和掩膜的词云图。后端负责读取 Excel 名单、处理掩膜、计算权重、布局、渲染和导出;前端提供参数面板、任务提交、结果查看、查找和导出入口。
本项目是一套中文词云生成+画布设计系统,核心能力:
当前项目不是通用设计平台。`Projects``Assets``Templates` 接口存在,但主要服务于当前工作台原型和素材管理,不代表完整生产级工程系统
1. **词云生成**:读取 Excel 名单,按掩膜轮廓填充人名,输出 PNG/SVG/DB/metrics
2. **画布设计**:前端提供图层化画布、贴纸库、基础形状编辑、SVG 总图导出。
3. **同底图换名单**:画布中已有的词云底图,支持只换名单不换位置和字号(保留视觉结构)。
4. **线距分析**:对 SVG 路径进行线距采样,辅助激光加工参数设定。
## 目录结构
| 路径 | 职责 |
| --- | --- |
| `backend/wordcloud_generate_hybrid.py` | CLI 入口,加载配置后调用生成管线 |
| `backend/core/config.py` | 默认配置、JSON 配置合并、CLI 覆盖、路径和字体解析 |
| `backend/core/pipeline.py` | 生成主流程:读数据、画布、掩膜、权重、布局、渲染、DB、metrics |
| `backend/core/layout.py` | Python 布局调度、字号打分、逐词放置、SVG 导出 |
| `backend/core/weights.py` | 笔画复杂度权重、Excel 权重、面积字号模型 |
| `backend/core/mask.py` | 掩膜归一化、自动画布、边界安全 padding |
| `backend/core/render.py` | 填充率计算和点阵补偿 |
| `backend/EfficientWordCloud/` | C++ 扩展及其 Python 包装 |
| `backend/service/` | FastAPI 服务、任务管理、文件存储 |
| `frontend/src/` | React 工作台 |
| `docs/` | 标准文档入口 |
```
wordcloud/
├── docs/ # 标准文档
│ ├── README.md
│ ├── PROJECT_STANDARD.md
│ ├── ALGORITHM.md
│ ├── CONFIG.md
│ ├── API.md
│ ├── CANVAS_STUDIO.md
│ ├── TESTING.md
│ └── DEPLOYMENT.md
├── backend/
│ ├── core/ # 词云核心引擎
│ │ ├── config.py # 配置默认值、别名、类型校验
│ │ ├── pipeline.py # 主流程:数据→掩膜→权重→布局→渲染→导出
│ │ ├── layout.py # Python 布局调度、SVG 路径导出
│ │ ├── weights.py # 笔画权重、Excel 权重、面积字号模型
│ │ ├── mask.py # 掩膜生成、归一化、自动画幅
│ │ ├── render.py # 高清精修、填充率计算、重叠检测
│ │ ├── fonts.py # 字体缓存
│ │ └── ewc.py # 兼容层(基类+Python 稀疏网格)
│ ├── EfficientWordCloud/ # C++ 扩展(integral grid + 精确字形碰撞)
│ │ ├── efficient_wordcloud/
│ │ │ ├── wordcloud.py # Python 包装
│ │ │ └── src/ewc_core.cpp # Cython 扩展
│ │ └── setup.py # C++ 编译入口
│ ├── service/ # FastAPI 服务
│ │ ├── app.py # HTTP 路由
│ │ ├── schemas.py # Pydantic 模型
│ │ ├── runner.py # 子进程任务执行
│ │ ├── job_manager.py # 内存任务状态管理
│ │ ├── storage.py # 任务文件目录管理
│ │ ├── line_spacing.py # SVG 线距分析
│ │ └── log_config.py # 服务日志配置
│ ├── tests/ # 单元测试
│ │ └── test_layout_constraints.py
│ ├── tools/ # 基准工具
│ │ └── benchmark_layout.py
│ ├── service_workspace/ # 运行时任务产物(.gitignore
│ ├── service_assets/ # 后端素材库(.gitignore
│ ├── service_projects/ # 后端工程项目(.gitignore
│ ├── service_design_templates/ # 设计模板(.gitignore
│ └── start-dev.sh # 本地后端启动脚本
├── frontend/
│ ├── src/
│ │ ├── App.tsx # 页面路由:home → canvas / wordcloud / help
│ │ ├── main.tsx
│ │ ├── types.ts # 全项目 TypeScript 类型
│ │ ├── styles.css
│ │ ├── pages/ # 页面级组件
│ │ │ ├── TemplateHome.tsx # 首页:模板选择
│ │ │ ├── CanvasStudio.tsx # 画布设计页
│ │ │ ├── TestWorkbench.tsx # 词云生成页
│ │ │ └── HelpPage.tsx # 帮助页
│ │ ├── components/ # 可复用组件
│ │ │ ├── AdvancedPanel.tsx
│ │ │ ├── CanvasArea.tsx
│ │ │ ├── DockTabBar.tsx
│ │ │ ├── EditPanel.tsx
│ │ │ ├── ExportPanel.tsx
│ │ │ ├── FindPanel.tsx
│ │ │ ├── FloatingPanel.tsx
│ │ │ ├── ImportPanel.tsx
│ │ │ ├── ProgressPanel.tsx
│ │ │ ├── ViewControls.tsx
│ │ │ └── AppSettingsWindow.tsx
│ │ ├── hooks/
│ │ ├── lib/
│ │ │ ├── api.ts # API 工具函数
│ │ │ ├── canvasDocument.ts # 画布模型操作
│ │ │ ├── stickerLibrary.ts # 贴纸库存取
│ │ │ ├── svgExport.ts # SVG/Zip 序列化
│ │ │ └── templateLibrary.ts # 模板库
│ │ └── vite-env.d.ts
│ ├── vite.config.ts
│ ├── package.json
│ └── tsconfig.json
├── start-all.sh # 一键前后端联调
├── install-ubuntu.sh # Ubuntu Docker 一键部署
├── release/
│ └── install-ubuntu.sh
├── scripts/
│ └── pack-release.sh
└── README.md / README_zh.md # 顶层面向用户说明(非标准)
```
## 运行方式
### 一键前后端联调
在项目根目录运行:
### 一键前后端联调(推荐开发用)
```bash
./start-all.sh
```
它会:
- 启动 `backend/start-dev.sh`
- 启动前端 `npm run dev`
- 默认后端端口为 `8000`
- 前端 Vite 端口为 `3000`
- 后端:http://localhost:8000
- 前端:http://localhost:3000
- 两者通过前端 Vite 代理通信(配置见 `frontend/vite.config.ts`
### 单独启动后端
@@ -48,7 +118,11 @@ cd backend
./start-dev.sh
```
`start-dev.sh` 会检查 Python 依赖、必要时创建 `.venv`,并在 C++ 源码更新后重新编译 `ewc_core`
脚本会自动:
- 探测 Python 3.9+(优先系统 Python,否则创建 `.venv`
- 检查并安装 `fastapi uvicorn python-multipart pydantic pandas openpyxl pillow numpy matplotlib`
- 在外部 Python 中复用 `scipy`ABI 匹配时),避免网络安装
- C++ 扩展 `ewc_core` 源码有更新时自动重新编译
### 单独启动前端
@@ -57,8 +131,6 @@ cd frontend
npm run dev
```
前端通过 Vite 代理访问后端。代理配置见 `frontend/vite.config.ts`
### CLI 生成
```bash
@@ -66,81 +138,73 @@ cd backend
python wordcloud_generate_hybrid.py --config /path/to/config.json
```
CLI 配置优先级
1. `backend/core/config.py` 默认值
2. JSON 配置文件
3. CLI 参数覆盖
CLI 配置优先级见 [CONFIG.md](CONFIG.md)。
## 输入要求
### Excel 名单
服务接口接受 `.xlsx`。默认名单列为 `DATA_COL_INDEX = 1`,也就是第 2 列,索引从 0 开始。
- 服务接口接受 `.xlsx`
- `DATA_COL_INDEX` 默认 `1`(第2列,0-based
- `REMOVE_DUPLICATES` 默认 `False`,前端也默认保留重复
- 权重列:优先 `WEIGHT_COL_NAME`,次选 `WEIGHT_COL_INDEX`
- 有效权重必须为正数
`REMOVE_DUPLICATES = False` 时,Excel 中重复姓名会保留。当前前端默认保留重复。
### 权重来源
### 权重
权重来源按优先级合并:
1. Excel 权重列:`WEIGHT_COL_NAME` 优先于 `WEIGHT_COL_INDEX`
2. 笔画复杂度权重:受 `ENABLE_STROKE_WEIGHTS` 控制
3. 默认权重:没有权重时使用 `10`
关闭 `ENABLE_STROKE_WEIGHTS` 且不传 Excel 权重列时,所有姓名进入均等权重。
| 场景 | 行为 |
|------|------|
| 有 Excel 权重,笔画权重开启 | Excel 值作为基础 × 笔画复杂度归一化乘数 |
| 有 Excel 权重,笔画权重关闭 | 仅 Excel 权重 |
| 无 Excel 权重,笔画权重开启 | 仅笔画复杂度权重 |
| 两者均无 | 全部默认权重 `10` |
### 掩膜
`MODE = IMAGE` 时必须提供 PNG/JPG/JPEG 掩膜。后端会将图片转灰度并以阈值 `200` 二值化。
`FILL_ON = BLACK` 时,黑色区域可填充;`FILL_ON = WHITE` 时,白色区域可填充
- `MODE=IMAGE` 时必须上传 PNG/JPG/JPEG 掩膜文件
- 后端转灰度后按阈值 `200` 二值化
- `FILL_ON=BLACK`黑色区域可填充;`FILL_ON=WHITE`白色区域可填充
- `MODE=TEXT`:用指定文字生成文本掩膜
## 输出产物
每次服务任务会创建
服务任务产物位置
```text
```
backend/service_workspace/{job_id}/
input/
mask.png
names.xlsx
output/
Efficient_Result_HD_AutoResize.png
Efficient_Result_HD_AutoResize.svg
Efficient_Result_HD_AutoResize_stroke.svg
wordcloud_hd.db
metrics.json
debug/
mask_src.png
mask_hd.png
mask_small.png
occ_fast.png
Efficient_Result_HD_AutoResize.png # 最终位图
Efficient_Result_HD_AutoResize.svg # 填充 SVG
Efficient_Result_HD_AutoResize_stroke.svg # 描边 SVG(激光雕刻适用)
wordcloud_hd.db # SQLiteword_locations 表
metrics.json # 运行指标
debug/ # 调试图(受 SAVE_DEBUG_IMAGES 控制)
config.json
```
产物说明:
| 文件 | 含义 |
| --- | --- |
| PNG | 最终位图结果 |
| SVG | 填充路径 SVG |
| `_stroke.svg` | 描边 SVG,适合继续加工 |
| SQLite DB | `word_locations` 表,记录词语位置、字号、颜色、方向、包围盒 |
| metrics | 运行指标、画布尺寸、填充率、配置快照 |
| debug | 调试图,受 `SAVE_DEBUG_IMAGES` 控制 |
## 当前限制
- 重复填充当前按名单轮次展开,但单个词在放置失败时会独立降字号;这会导致后几轮整体字号小于前几轮
- `reorder_stratified()` 当前对主路径 `query_direct()` 没有实际影响,因为 `query_direct()` 不使用 `valid_coords`
- C++ `Grid_query_direct` 的 GIL 释放包装没有包住实际扫描调用,性能并发上还有优化空间
- API 中的 Jobs 存储在进程内存,服务重启后历史任务状态会丢失;文件仍保留在 `service_workspace`
- 旧文档中的市场分析、路线图和性能宣传不作为当前能力承诺
- **名单完整性是不可关闭的硬约束。** 放不下的情况下只会整批缩放字号或扩大画布,不会漏词
- 不存在逐词缩字号、gap filling 或大字号自动压缩路径
- `USER_MIN_FONT_SIZE``USER_MAX_FONT_SIZE` 是不可越过的边界;冲突时直接报错
- `SIZE_RATIO=1` 时所有同权重词语保持完全相同字号,面积估算和重试阶段都维持单一字号
- C++ 中保留旧矩形查询 API 供底层兼容性,正式流水线使用 `place_glyph_exact()` 真实字形主路径
- 前端贴纸库和画布文档只保存在当前浏览器 `localStorage`,不跨设备同步。
- Jobs 状态存在内存中,服务重启后历史任务状态丢失;文件仍保留在 `service_workspace`
- 当前 Projects、Assets、Templates 接口主要服务当前工作台原型和素材管理,不代表完整生产级工程系统。
## 维护约定
- 修改算法行为时,同步更新 [ALGORITHM.md](ALGORITHM.md)。
- 新增或删除配置项时,同步更新 [CONFIG.md](CONFIG.md)。
- 改 HTTP 接口或响应模型时,同步更新 [API.md](API.md)。
- 不再新增单次变更记录文档;短期变更应合并进标准文档。
| 变更范围 | 同步更新 |
|----------|----------|
| 算法行为 | ALGORITHM.md |
| 配置项增删 | CONFIG.md |
| HTTP 接口/响应模型 | API.md |
| 前端画布功能 | CANVAS_STUDIO.md |
| 测试/基准 | TESTING.md |
| 部署方式 | DEPLOYMENT.md |
不再新增单次变更记录文档;短期变更合并进标准文档。