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
+109
View File
@@ -0,0 +1,109 @@
# 测试与基准
本文档覆盖当前代码中的测试工具和基准脚本。
## 单元测试
位置:`backend/tests/test_layout_constraints.py`
运行方式:
```bash
cd backend
python -m pytest tests/test_layout_constraints.py -v
```
测试矩阵(基于 `unittest`,不依赖外部服务):
| 测试 | 目的 |
|------|------|
| `test_size_ratio_one_keeps_every_equal_weight_size_identical` | `SIZE_RATIO=1` 时所有同权重词字号完全相同 |
| `test_explicit_equal_min_max_is_exact` | `USER_MIN=USER_MAX=12` 时所有词精确为 `12px` |
| `test_same_weight_groups_receive_the_same_size` | 相同权重组内字号一致;权重组间字号递增 |
| `test_stroke_weight_is_applied_when_excel_weights_are_flat` | Excel 权重全为 `1` 时,笔画权重仍能产生区分度 |
| `test_largest_empty_square_ignores_space_outside_mask` | 最大空洞算法只计算掩膜内区域 |
| `test_conflicting_explicit_font_bounds_fail` | `MIN > MAX` 时报错而非静默回退 |
| `test_explicit_max_overrides_automatic_readability_floor` | 用户覆盖最大字号时,自动可读性下限让位于用户输入 |
| `test_base_class_never_uses_a_private_fallback_size` | 基类布局不使用隐藏回退字号 |
| `test_rendered_ink_stays_inside_mask_and_does_not_overlap` | 工作网格上:墨迹不超出掩膜、不重叠 |
| `test_hd_rendered_ink_does_not_overlap_after_scaling` | 高清放大后:零重叠像素、零碰撞边距 |
固定种子(`SEED=LAYOUT_SEED=20260718`)确保可复现。
## 基准测试
位置:`backend/tools/benchmark_layout.py`
运行方式:
```bash
cd backend
python tools/benchmark_layout.py --counts 80 800 --canvas 2000 --assert-targets
```
参数:
| 参数 | 说明 |
|------|------|
| `--counts COUNT [COUNT ...]` | 测试名单数量,默认 `80 800` |
| `--canvas CANVAS` | 画布尺寸,默认 `2000` |
| `--max-growth-rounds N` | 最大画布扩展轮数,默认 `1` |
| `--assert-targets` | 启用门禁检查 |
| `--output-dir PATH` | 输出目录,默认 `backend/benchmark_outputs/` |
### 门禁检查项(`--assert-targets`
| 指标 | 阈值 | 含义 |
|------|------|------|
| `completeness` | `= 1.0` | 名单必须全部放入 |
| `equal_weight_font_consistent` | `True` | 等权重时字号一致 |
| `hd_overlap_pixels` | `= 0` | 高清渲染后零重叠 |
| `contour_grid_coverage` | `≥ 0.80` | 轮廓网格覆盖率(避免大块空洞) |
| `hd_true_density` | `≥ 0.10` | 真实笔画密度 |
| `total_seconds` | `< 1.0s` (count<100) / `< 5.0s` (count<1000) | 性能门槛 |
### 输出指标
| 指标 | 说明 |
|------|------|
| `count` | 名单数量 |
| `canvas` | 最终画布尺寸 |
| `canvas_growth_rounds` | 画布扩展轮数 |
| `placed` | 实际放置词数 |
| `completeness` | 完整率 |
| `layout_seconds` / `render_seconds` / `total_seconds` | 各阶段耗时 |
| `work_fill_ratio` | 工作网格填充率 |
| `font_size_min` / `font_size_max` | 字号范围 |
| `equal_weight_font_consistent` | 等权重字号一致性 |
| `collision_margin` | 碰撞边距 |
| `hd_clearance_shifted_words` | 高清精修时位移词数 |
| `hd_clearance_max_shift` | 最大位移像素 |
| `hd_clearance_px` | 隔离带宽度 |
| `hd_clearance_priority_restarts` | 优先级回溯次数 |
| `hd_overlap_pixels` | 高清重叠像素(双重检查) |
| `largest_empty_square_work_px` | 工作网格最大空洞(像素) |
| `largest_empty_square_font_ratio` | 空洞相对字号比例 |
| `hd_true_density` | 高清真实笔画密度 |
| `ink_bbox_coverage` | 墨迹包围盒覆盖率 |
| `contour_grid_coverage` | 轮廓网格覆盖率 |
基准结果保存为 JSON`backend/benchmark_outputs/benchmark.json`
## 扩展基准
基准脚本使用 `run_generation_pass()` 直接调用核心管线,绕过 HTTP 服务和文件 IO。
- 生成数据:`make_names()` 使用中文姓氏库和双字名库组合出不重复姓名
- 掩膜:`make_round_mask()` 生成圆形掩膜
- 固定种子:`SEED=LAYOUT_SEED=20260718`
- 固定配置:`SIZE_RATIO=1.0, N_REPETITIONS=1, WORK_SCALE=0.18, TARGET_FILL_RATIO=0.45`
## CI 建议
```bash
cd backend
python -m pytest tests/test_layout_constraints.py -v
python tools/benchmark_layout.py --counts 80 800 --canvas 2000 --assert-targets
```
两次运行均应在数秒内完成。