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
+55 -22
View File
@@ -7,13 +7,14 @@
- 默认后端地址:`http://localhost:8000`
- 请求体中上传文件使用 `multipart/form-data`
- `params` 字段是 JSON 字符串,顶层必须是对象
- 任务状态存在内存中,服务重启后状态会丢失
- 任务状态存在内存中`JobManager`),服务重启后状态会丢失;文件仍保留在 `service_workspace`
- CORS 已开启,允许所有来源
## Jobs
### GET `/api/health`
返回:
健康检查。
```json
{"ok": true}
@@ -30,15 +31,13 @@
Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
|------|------|------|
| `name_list` | 是 | `.xlsx` 名单文件 |
| `mask_image` | IMAGE 模式必填 | `.png` / `.jpg` / `.jpeg` 掩膜 |
| `font_file` | 否 | 临时上传字体,支持后端 `_FONT_EXTENSIONS` 中的格式 |
| `font_file` | 否 | 临时上传字体`.ttf` / `.ttc` / `.otf` |
| `font_id` | 否 | 使用已上传字体 |
| `params` | 否 | JSON 字符串,合并到任务配置 |
字体格式当前支持 `.ttf``.ttc``.otf`
`params` 示例:
```json
@@ -129,10 +128,12 @@ SSE 事件流。事件数据模型:
### GET `/api/jobs/{job_id}/locations`
查询词语位置。查询参数:
查询词语位置。
查询参数:
| 参数 | 说明 |
| --- | --- |
|------|------|
| `name` | 可选;为空返回全部,非空精确匹配 |
返回:
@@ -173,7 +174,7 @@ SSE 事件流。事件数据模型:
查询参数:
| 参数 | 默认 | 说明 |
| --- | --- | --- |
|------|------|------|
| `fill` | `fill` | `fill` / `dot` / `line` / `ring` |
| `stroke` | `0` | 是否描边 |
| `spacing` | `10` | 点阵间距 |
@@ -192,11 +193,11 @@ SSE 事件流。事件数据模型:
返回后端硬编码模板列表:
- `poster_1x2`
- `poster_4x5`
- `poster_1x1`
- `poster_3x4`
- `poster_16x9`
- `poster_1x2`(竖版手机海报,1080×2160
- `poster_4x5`(社交媒体图,1080×1350
- `poster_1x1`(方形封面,1080×1080
- `poster_3x4`(竖版广告,1080×1440
- `poster_16x9`(横版电商,1920×1080
## Assets
@@ -205,7 +206,7 @@ SSE 事件流。事件数据模型:
上传素材。Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
|------|------|------|
| `file` | 是 | 素材文件 |
| `name` | 否 | 名称 |
| `type` | 否 | 默认 `upload` |
@@ -215,7 +216,7 @@ SSE 事件流。事件数据模型:
从任务产物导入素材。Form 字段:
| 字段 | 默认 | 说明 |
| --- | --- | --- |
|------|------|------|
| `kind` | `png` | 产物类型 |
| `name` | 空 | 素材名称 |
| `type` | `wordcloud` | 素材类型 |
@@ -243,10 +244,10 @@ SSE 事件流。事件数据模型:
### POST `/api/projects`
Form 字段:
创建工程。Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
|------|------|------|
| `name` | 是 | 工程名 |
| `template_id` | 是 | 模板 ID |
| `background_color` | 是 | `#RRGGBB` |
@@ -272,29 +273,61 @@ Form 字段:
### GET `/api/fonts`
返回字体列表,包含默认字体项。
返回字体列表,包含默认字体项`__default__`
### POST `/api/fonts`
上传字体。Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `file` | 是 | 字体文件 |
|------|------|------|
| `file` | 是 | 字体文件`.ttf` / `.ttc` / `.otf` |
| `name` | 否 | 字体名 |
### DELETE `/api/fonts/{font_id}`
删除已上传字体。默认字体不能删除。
## Line Spacing Analysis
### POST `/api/jobs/{job_id}/analyze-line-spacing`
分析 SVG 词云路径的线距,返回 `LineSpacingAnalysisSummary`
请求体(JSON):
```json
{
"percentile": 3,
"elementWidth": 100.0,
"elementHeight": 100.0,
"sampleStep": 2.0
}
```
响应字段:
| 字段 | 说明 |
|------|------|
| `percentile` | 线距百分位 |
| `spacingPx` | 采样线距(像素) |
| `spacingMm` | 采样线距(毫米) |
| `minSpacingPx` | 最小线距(像素) |
| `minSpacingMm` | 最小线距(毫米) |
| `curveCount` | 曲线数量 |
| `segmentCount` | 线段数量 |
| `sourceWidth` / `sourceHeight` | SVG 原始尺寸 |
| `elementWidth` / `elementHeight` | 目标元素尺寸 |
## 常见错误
| 场景 | 状态码 | detail |
| --- | --- | --- |
|------|--------|--------|
| `params` 不是合法 JSON | 400 | `params must be valid JSON` |
| `params` 不是对象 | 400 | `params must be JSON object` |
| `name_list``.xlsx` | 400 | `name_list must be xlsx` |
| IMAGE 模式缺少掩膜 | 400 | `mask_image is required when MODE=IMAGE` |
| 掩膜文件不存在 | 400 | `mask_image must be png/jpg/jpeg` |
| job 不存在 | 404 | `job not found` |
| 产物未就绪 | 404 | `artifact not ready` |
| 文件类型未知 | 404 | `unknown artifact kind` |