Initial project baseline

This commit is contained in:
2026-07-04 02:40:45 +08:00
commit d5d8caef2f
86 changed files with 15590 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
# 生成算法说明
本文档描述当前代码实际算法。核心代码位于 `backend/core/pipeline.py``backend/core/layout.py``backend/core/weights.py``backend/EfficientWordCloud/efficient_wordcloud/src/ewc_core.cpp`
## 总流程
1. 读取 Excel 名单:`pipeline.main()`
2. 计算自动画幅:`mask.calculate_dynamic_dimensions()`
3. 生成并归一化掩膜:`mask.prepare_mask()`
4. 计算权重:`weights.extract_weights_from_df()``weights.get_stroke_complexity_batch()`
5. 估算字号范围:`weights.calculate_font_by_area_model()`
6. 小画布布局:`layout.OptimizedEfficientWordCloud.generate_from_frequencies()`
7. C++ 找可放位置:`IntegralGrid.query_direct()`
8. C++ 写入字形占用:`IntegralGrid.stamp_and_rebuild()`
9. 计算填充率并必要时重试放大
10. 将小画布 layout 放大到高清画布并输出 PNG/SVG/DB/metrics
## 名单和重复填充
`N_REPETITIONS` 决定目标词数:
```text
total_target = len(names) * N_REPETITIONS
```
布局序列由 `_build_layout_sequence()` 生成。当前行为是按原名单循环追加:
```text
[A, B, C], N_REPETITIONS=4
=> [A, B, C, A, B, C, A, B, C, A, B, C]
```
所以当前队列顺序是“先填一轮名单,再填下一轮”,不是先放完同一个名字所有副本。
需要注意:队列顺序公平不等于最终字号完全一致。放置阶段如果某个词以目标字号找不到位置,会单独降字号继续尝试。因此后几轮词语通常比前几轮小。
## 权重逻辑
### Excel 权重
`WEIGHT_COL_NAME` 优先于 `WEIGHT_COL_INDEX`。有效权重必须是可转数字且大于 0。
`REMOVE_DUPLICATES = True` 时,同名权重取最大值。`REMOVE_DUPLICATES = False` 时,仍会按名字聚合权重映射,所以同名不同权重不会保留为不同权重实例。
### 笔画权重
`ENABLE_STROKE_WEIGHTS = True` 时,系统渲染每个字符到 64x64 灰度图,用像素占用量估算复杂度。一个名字的笔画权重取其中最复杂字符的值。
`ENABLE_STROKE_WEIGHTS = False` 时跳过笔画权重。若没有 Excel 权重,所有名字权重默认为 `10`
## 字号范围估算
`calculate_font_by_area_model()` 使用可填充面积、目标填充率、packing efficiency、重复次数和名字长度估算 `min_font` / `max_font`
公式思想:
- 可填区域越大,字号越大
- 名字越多、重复次数越高,字号越小
- 字符越多,总占用质量越高,字号越小
- 权重越高,在 `log1p(weight)` 归一化后获得更高面积质量
最终 `max_font` 基于 `min_font * SIZE_RATIO` 计算。
## 字号打分
当前 `build_log_rank_scores(..., per_word=True)` 会按姓名权重计算固定分数,然后映射到展开后的重复序列。
这意味着:
- 同名副本的目标分数相同
- 同名副本的初始目标字号相同
- 权重相同时,所有姓名初始目标字号相同
但最终放置字号仍可能变小,因为放置失败时会逐词降字号。
## 放置策略
每个词的放置流程:
1. 根据目标分数得到目标字号
2. 随机决定横排或竖排
3. 用 PIL 测量文字包围盒
4. 调用 C++ `query_direct(query_h, query_w, seed)` 找位置
5. 如果找不到,字号减 2 后重试,最低到目标字号的 40% 或 `min_font_size`
6. 放置成功后,取真实字形 bitmap 并调用 `stamp_and_rebuild()`
7. 主循环放不下的词进入 gap filling,用更小字号再尝试一次
## C++ 积分图搜索
C++ `IntegralGrid` 维护两个核心结构:
- `canvas`:真实占用像素,`1` 表示已占用或掩膜阻挡
- `data``canvas` 的积分图,用于 O(1) 判断矩形区域是否为空
`query_direct()` 的行为:
1. 如果画布完全空,随机返回一个位置
2. 先随机探测最多 16 个位置
3. 如果未命中,扫描所有可能位置
4. 对每个候选位置用积分图判断包围盒是否为空
5. 从所有可放位置中随机选一个
`stamp_and_rebuild()` 的行为:
1. 把真实字形像素写入 C++ `canvas`
2. 从字形左上角开始局部重建积分图
当前碰撞检测是“矩形找位置 + 字形像素落图”。找位置阶段要求文字包围盒矩形完全空;实际占用阶段只写入字形像素。
## 填充率重试
一次布局完成后,`compute_fill_ratio_fast()` 重新渲染 layout 并计算填充率。
如果填充率低于 `MIN_ACCEPT_FILL_RATIO`,管线会尝试二分放大 `size_scale`,并可通过 `FILL_RETRY_RELAX_LARGE_CAP` 放宽大字号限制。
如果仍无法达到目标,会保留填充率最好的 layout。
## 已知算法限制
- 当前没有真正的“整轮统一降字号”机制。重复填充虽然按轮展开,但每个词可以独立降字号。
- `LIMIT_LARGE_FONTS` 是全局计数,不区分姓名和轮次。
- `LAYOUT_ORDER_MODE_INTERLEAVED_RANDOM` 只改变展开序列的顺序,不改变 C++ 的空间采样策略。
- `ENABLE_STRATIFIED_SAMPLING` 调用的 `reorder_stratified()``query_direct()` 主路径无效。
- C++ `batch_query()` 会用矩形 `update_rect_add()` 更新,不走真实字形 `stamp_and_rebuild()`;当前 Python 主路径没有使用它。
## 后续公平重复填充建议
如果目标是“每一轮名单整体公平变小”,建议新增独立模式,而不是继续微调当前逐词降字号:
- `REPEAT_FILL_MODE = "ROUND_ROBIN_FAIR"`
- 以轮为单位生成任务
- 同一轮使用统一字号或统一权重映射
- 某一轮放不下时,整轮降低字号重试
- 失败词统一进入下一档补位队列
- 大字号限制按姓名或轮次计数
+300
View File
@@ -0,0 +1,300 @@
# 后端 API 说明
本文档按 `backend/service/app.py``backend/service/schemas.py` 当前代码整理。
## 基础约定
- 默认后端地址:`http://localhost:8000`
- 请求体中上传文件使用 `multipart/form-data`
- `params` 字段是 JSON 字符串,顶层必须是对象
- 任务状态存在内存中,服务重启后状态会丢失
## Jobs
### GET `/api/health`
返回:
```json
{"ok": true}
```
### GET `/api/jobs`
返回内存中的任务状态列表,最新任务在前。
### POST `/api/jobs`
创建词云任务。
Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `name_list` | 是 | `.xlsx` 名单文件 |
| `mask_image` | IMAGE 模式必填 | `.png` / `.jpg` / `.jpeg` 掩膜 |
| `font_file` | 否 | 临时上传字体,支持后端 `_FONT_EXTENSIONS` 中的格式 |
| `font_id` | 否 | 使用已上传字体 |
| `params` | 否 | JSON 字符串,合并到任务配置 |
字体格式当前支持 `.ttf``.ttc``.otf`
`params` 示例:
```json
{
"MODE": "IMAGE",
"DATA_COL_INDEX": 1,
"SEED": 42,
"N_REPETITIONS": 20,
"ENABLE_STROKE_WEIGHTS": false,
"FONT_COLOR": "#000000"
}
```
响应:
```json
{"job_id": "..." }
```
### GET `/api/jobs/{job_id}`
返回 `JobStatus`
```json
{
"job_id": "...",
"status": "queued|running|success|failed",
"stage": "...",
"progress_percent": 0,
"message": "...",
"created_at": "...",
"updated_at": "...",
"artifacts": {},
"error": ""
}
```
### GET `/api/jobs/{job_id}/detail`
返回状态和最近事件:
```json
{
"status": {},
"recent_events": []
}
```
### GET `/api/jobs/{job_id}/events`
SSE 事件流。事件数据模型:
```json
{
"type": "log|status",
"stage": "placing_words",
"progress_percent": 65,
"message": "...",
"timestamp": "..."
}
```
### GET `/api/jobs/{job_id}/result`
返回可下载产物 URL
```json
{
"job_id": "...",
"status": "success",
"image_url": "/api/jobs/{job_id}/files/png",
"svg_url": "/api/jobs/{job_id}/files/svg",
"svg_stroke_url": "/api/jobs/{job_id}/files/svg_stroke",
"db_url": "/api/jobs/{job_id}/files/db",
"metrics_url": "/api/jobs/{job_id}/files/metrics"
}
```
### GET `/api/jobs/{job_id}/files/{kind}`
下载产物。`kind` 支持:
- `png`
- `svg`
- `svg_stroke`
- `db`
- `metrics`
### GET `/api/jobs/{job_id}/locations`
查询词语位置。查询参数:
| 参数 | 说明 |
| --- | --- |
| `name` | 可选;为空返回全部,非空精确匹配 |
返回:
```json
{
"job_id": "...",
"query": "",
"total": 1,
"canvas_width": 8000,
"canvas_height": 4000,
"matches": [
{
"id": 1,
"name": "张三",
"x": 100,
"y": 200,
"font_size": 64,
"color": "#000000",
"orientation": "horizontal",
"box_x": 100,
"box_y": 200,
"box_width": 120,
"box_height": 50
}
]
}
```
### GET `/api/jobs/{job_id}/occupancy_mask`
返回 PNG,显示每个已放置词语的 bounding box。
### GET `/api/jobs/{job_id}/custom.svg`
按已生成 DB 重新导出 SVG。
查询参数:
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| `fill` | `fill` | `fill` / `dot` / `line` / `ring` |
| `stroke` | `0` | 是否描边 |
| `spacing` | `10` | 点阵间距 |
| `radius` | `2` | 点阵半径 |
| `color` | `#000000` | 输出颜色 |
| `line_spacing` | `6` | 线填充间距 |
| `line_width` | `1` | 线宽 |
| `line_angle` | `0` | 线角度 |
| `ring_radius` | `3` | 环半径 |
| `ring_width` | `1` | 环线宽 |
| `ring_spacing` | `8` | 环间距 |
## Templates
### GET `/api/templates`
返回后端硬编码模板列表:
- `poster_1x2`
- `poster_4x5`
- `poster_1x1`
- `poster_3x4`
- `poster_16x9`
## Assets
### POST `/api/assets`
上传素材。Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `file` | 是 | 素材文件 |
| `name` | 否 | 名称 |
| `type` | 否 | 默认 `upload` |
### POST `/api/assets/from-job/{job_id}`
从任务产物导入素材。Form 字段:
| 字段 | 默认 | 说明 |
| --- | --- | --- |
| `kind` | `png` | 产物类型 |
| `name` | 空 | 素材名称 |
| `type` | `wordcloud` | 素材类型 |
### GET `/api/assets`
列出素材。支持查询参数:
- `type`
- `job_id`
### GET `/api/assets/{asset_id}`
获取素材元数据。
### GET `/api/assets/{asset_id}/download`
下载素材文件。
### DELETE `/api/assets/{asset_id}`
删除素材。
## Projects
### POST `/api/projects`
Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `name` | 是 | 工程名 |
| `template_id` | 是 | 模板 ID |
| `background_color` | 是 | `#RRGGBB` |
| `stickers` | 否 | JSON 数组字符串 |
### GET `/api/projects`
返回工程摘要列表。
### GET `/api/projects/{project_id}`
返回工程完整数据。
### PATCH `/api/projects/{project_id}`
按传入 Form 字段部分更新工程。
### DELETE `/api/projects/{project_id}`
删除工程。
## Fonts
### GET `/api/fonts`
返回字体列表,包含默认字体项。
### POST `/api/fonts`
上传字体。Form 字段:
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `file` | 是 | 字体文件 |
| `name` | 否 | 字体名 |
### DELETE `/api/fonts/{font_id}`
删除已上传字体。默认字体不能删除。
## 常见错误
| 场景 | 状态码 | 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` |
| job 不存在 | 404 | `job not found` |
| 产物未就绪 | 404 | `artifact not ready` |
| 文件类型未知 | 404 | `unknown artifact kind` |
+67
View File
@@ -0,0 +1,67 @@
# 画布与贴纸功能
本文档描述当前前端代码中已经实现的画布设计功能。事实来源是 `frontend/src/App.tsx``frontend/src/pages/CanvasStudio.tsx``frontend/src/components/ExportPanel.tsx``frontend/src/lib/stickerLibrary.ts``frontend/src/types.ts`
## 页面关系
- 应用默认进入画布设计页。
- 画布页顶部的“添加词云”会切换到原有词云生成页。
- 词云生成页顶部的“返回画布”会回到画布设计页。
- 词云生成页的导出面板保留下载 SVG/位图功能,并新增“作为贴纸导入贴纸库”。
## 贴纸库
贴纸库是前端本地能力,不依赖后端接口:
- 存储位置:`localStorage``wordcloud-sticker-library`
- 数据类型:`StickerAsset`,当前支持 `svg``image` 两类,已实现入口主要使用 `svg`
- 用户导入 SVG:画布页左侧“贴纸”面板读取 `.svg` 文件文本,写入贴纸库,并立即插入画布。
- 词云作为贴纸:导出面板按当前 SVG 导出参数请求 `/api/jobs/{job_id}/custom.svg`,读取返回的 SVG 文本后写入贴纸库。
- 贴纸删除只删除本地贴纸库记录,不会删除已经导出的总图文件。
## 画布模型
画布文档保存在 `localStorage``wordcloud-canvas-document`
当前模型字段:
- `width`:画布宽度,默认 `1600`
- `height`:画布高度,默认 `1000`
- `background`:画布背景色,默认 `#ffffff`
- `elements`:画布元素数组。
当前元素类型:
- `sticker`:引用贴纸库中的 SVG 或图片。
- `text`:普通文字,支持内容、颜色、字号、字体、字重、位置、尺寸、旋转、透明度。
- `rect`:矩形,支持填充、描边、描边宽度、位置、尺寸、旋转、透明度。
- `ellipse`:椭圆,支持填充、描边、描边宽度、位置、尺寸、旋转、透明度。
- `line`:线条,支持描边、描边宽度、位置、尺寸、旋转、透明度。
## 编辑行为
- 点击贴纸库中的贴纸会把该贴纸插入画布中央区域。
- 画布元素可拖拽移动。
- 选中元素后可通过右下角手柄调整大小。
- 右侧属性面板可以精确编辑位置、尺寸、旋转、透明度和元素特有属性。
- 右侧属性面板提供上移、下移和删除。
- 画布面板支持修改画布宽高、背景色、导出 SVG、清空画布。
## SVG 导出
“导出总图 SVG”由前端序列化当前画布模型完成:
- 导出文件名:`canvas-design.svg`
- 背景输出为一个覆盖全画布的 `<rect>`
- 贴纸输出为 `<image>`SVG 贴纸会以内联 `data:image/svg+xml` 的形式嵌入。
- 文字输出为 `<text>`
- 基础形状输出为原生 SVG 的 `<rect>``<ellipse>``<line>`
- 元素的位移和旋转写入 SVG `transform`,透明度写入 `opacity`
## 当前边界
- 贴纸库和画布文档只保存在当前浏览器本地,不会跨浏览器或跨设备同步。
- 当前没有服务端素材库、项目文件格式或协作编辑接口。
- SVG 导入按用户信任文件处理;编辑器预览使用图片方式加载,不在页面中直接执行 SVG 内容。
- 当前缩放只影响编辑视图,不改变导出尺寸。
- 当前导出目标是 SVG;没有在画布页实现 PNG/JPG 总图导出。
+104
View File
@@ -0,0 +1,104 @@
# 配置说明
本文档只覆盖当前代码中实际可用的配置。完整键集合以 `backend/core/config.py``KNOWN_CONFIG_KEYS` 为准。
## 配置来源和优先级
CLI 入口 `backend/wordcloud_generate_hybrid.py` 的顺序:
1. 加载 `backend/core/config.py` 默认值
2. 如果传 `--config`,调用 `apply_json_config()`
3. 调用 `apply_cli_overrides()`
4. 调用 `finalize_runtime_config()` 派生路径、字体、输出路径
5. 调用 `set_random_seed()`
服务模式下,`POST /api/jobs` 会生成任务配置并写入:
```text
backend/service_workspace/{job_id}/config.json
```
之后由子进程通过 `--config` 读取。
## 前端参数映射
当前前端 `TestWorkbench.tsx` 提交的关键字段:
| 前端字段 | 后端配置 |
| --- | --- |
| `dataColIndex` | `DATA_COL_INDEX` |
| `seed` | `SEED` |
| `weightColIndex` | `WEIGHT_COL_INDEX` |
| `fontColor` | `FONT_COLOR` |
| `nRepetitions` | `N_REPETITIONS` |
| `strokeWeights=false` | `ENABLE_STROKE_WEIGHTS=false` |
前端只在重复次数大于 1 时传 `N_REPETITIONS`,只在关闭笔画权重时传 `ENABLE_STROKE_WEIGHTS=false`
## 常用配置
| 键 | 默认值 | 说明 |
| --- | --- | --- |
| `MODE` | `IMAGE` | 掩膜模式,`IMAGE``TEXT` |
| `MASK_IMAGE_PATH` | `7887.png` | IMAGE 模式掩膜路径,服务模式会覆盖为上传文件路径 |
| `IMAGE_CANVAS_MODE` | `WIDTH` | 图片掩膜缩放模式 |
| `FILL_ON` | `BLACK` | `BLACK` 表示黑色可填,`WHITE` 表示白色可填 |
| `EXCEL_PATH` | `四个方向汇总录取名单.xlsx` | Excel 路径,服务模式会覆盖为上传文件路径 |
| `DATA_COL_INDEX` | `1` | 名单列,0-based |
| `WEIGHT_COL_INDEX` | `None` | 权重列,0-based |
| `WEIGHT_COL_NAME` | `None` | 权重列名,优先于列索引 |
| `REMOVE_DUPLICATES` | `False` | 是否对名单去重 |
| `ENABLE_STROKE_WEIGHTS` | `True` | 是否在无 Excel 权重时使用笔画复杂度权重 |
| `N_REPETITIONS` | `1` | 名单重复倍率 |
| `SIZE_RATIO` | `2.0` | `max_font` 相对 `min_font` 的比例 |
| `PACKING_EFFICIENCY` | `0.85` | 面积模型中的打包效率 |
| `MIN_ACCEPT_FILL_RATIO` | `0.75` | 填充率重试阈值 |
| `REQUIRE_ALL_WORDS` | `True` | 搜索阶段是否优先要求达到目标词数 |
| `USER_MIN_FONT_SIZE` | `None` | 用户覆盖最小字号 |
| `USER_MAX_FONT_SIZE` | `None` | 用户覆盖最大字号 |
| `FONT_SCALE_MIN` | `0.5` | 二分搜索缩放下限 |
| `FONT_SCALE_MAX` | `1.2` | 二分搜索缩放上限 |
| `LIMIT_LARGE_FONTS` | `True` | 是否限制大字号数量 |
| `LARGE_FONT_LIMIT_RATIO` | `0.2` | 大字号数量上限占比 |
| `LARGE_FONT_THRESHOLD_RATIO` | `0.8` | 超过有效最大字号该比例视为大字号 |
| `LARGE_FONT_CAP_RATIO` | `0.6` | 超过大字号限制后的降级比例 |
| `ENABLE_DOT_MATRIX` | `False` | 是否用点阵补偿空白区域 |
| `CANVAS_RETRY_MAX_ROUNDS` | `1` | 画布扩大重试轮数 |
| `FONT_COLOR` | `#000000` | 固定字体颜色;为空时使用调色板 |
| `SEED` | `None` | 随机种子 |
| `LAYOUT_ORDER_MODE` | `SORTED` | 展开序列排序模式 |
| `LAYOUT_SEED` | `None` | 布局顺序种子,默认继承 `SEED` |
## JSON 别名
`apply_json_config()` 支持部分小写别名:
| 别名 | 正式配置 |
| --- | --- |
| `seed` | `SEED` |
| `layout_order_mode` | `LAYOUT_ORDER_MODE` |
| `layout_seed` | `LAYOUT_SEED` |
| `excel_path` | `EXCEL_PATH` |
| `mask_image_path` | `MASK_IMAGE_PATH` |
| `output_dir` | `OUTPUT_DIR` |
| `output_prefix` | `OUTPUT_PREFIX` |
| `mode` | `MODE` |
| `work_scale` | `WORK_SCALE` |
| `weight_col_index` | `WEIGHT_COL_INDEX` |
| `weight_col_name` | `WEIGHT_COL_NAME` |
| `min_font_size` | `USER_MIN_FONT_SIZE` |
| `max_font_size` | `USER_MAX_FONT_SIZE` |
| `font_color` | `FONT_COLOR` |
| `stroke_weights` | `ENABLE_STROKE_WEIGHTS` |
CLI 覆盖只支持 `parse_args()` 中定义的参数,不支持 `font_color``stroke_weights` CLI 参数。
## 类型校验
`CRITICAL_TYPE_CHECKS` 中的配置类型错误会直接退出。未知配置键只告警,不会失败。
## 路径规则
相对路径会以 `backend` 目录作为基准解析。输出路径会在 `finalize_runtime_config()` 中创建。
字体会先尝试项目字体,再尝试 `FONT_FALLBACK_PATHS`。字体不可用会直接失败。
+146
View File
@@ -0,0 +1,146 @@
# 项目标准说明
本文档按当前代码整理,覆盖项目边界、运行方式、输入输出和维护约定。最后核对代码时间:2026-06-09。
## 项目目标
本项目生成基于名单和掩膜的词云图。后端负责读取 Excel 名单、处理掩膜、计算权重、布局、渲染和导出;前端提供参数面板、任务提交、结果查看、查找和导出入口。
当前项目不是通用设计平台。`Projects``Assets``Templates` 接口存在,但主要服务于当前工作台原型和素材管理,不代表完整生产级工程系统。
## 目录结构
| 路径 | 职责 |
| --- | --- |
| `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/` | 标准文档入口 |
## 运行方式
### 一键前后端联调
在项目根目录运行:
```bash
./start-all.sh
```
它会:
- 启动 `backend/start-dev.sh`
- 启动前端 `npm run dev`
- 默认后端端口为 `8000`
- 前端 Vite 端口为 `3000`
### 单独启动后端
```bash
cd backend
./start-dev.sh
```
`start-dev.sh` 会检查 Python 依赖、必要时创建 `.venv`,并在 C++ 源码更新后重新编译 `ewc_core`
### 单独启动前端
```bash
cd frontend
npm run dev
```
前端通过 Vite 代理访问后端。代理配置见 `frontend/vite.config.ts`
### CLI 生成
```bash
cd backend
python wordcloud_generate_hybrid.py --config /path/to/config.json
```
CLI 配置优先级:
1. `backend/core/config.py` 默认值
2. JSON 配置文件
3. CLI 参数覆盖
## 输入要求
### Excel 名单
服务接口只接受 `.xlsx`。默认名单列为 `DATA_COL_INDEX = 1`,也就是第 2 列,索引从 0 开始。
`REMOVE_DUPLICATES = False` 时,Excel 中重复姓名会保留。当前前端默认保留重复。
### 权重
权重来源按优先级合并:
1. Excel 权重列:`WEIGHT_COL_NAME` 优先于 `WEIGHT_COL_INDEX`
2. 笔画复杂度权重:受 `ENABLE_STROKE_WEIGHTS` 控制
3. 默认权重:没有权重时使用 `10`
关闭 `ENABLE_STROKE_WEIGHTS` 且不传 Excel 权重列时,所有姓名进入均等权重。
### 掩膜
`MODE = IMAGE` 时必须提供 PNG/JPG/JPEG 掩膜。后端会将图片转灰度并以阈值 `200` 二值化。
`FILL_ON = BLACK` 时,黑色区域可填充;`FILL_ON = WHITE` 时,白色区域可填充。
## 输出产物
每次服务任务会创建:
```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
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`
- 旧文档中的市场分析、路线图和性能宣传不作为当前能力承诺。
## 维护约定
- 修改算法行为时,同步更新 [ALGORITHM.md](ALGORITHM.md)。
- 新增或删除配置项时,同步更新 [CONFIG.md](CONFIG.md)。
- 改 HTTP 接口或响应模型时,同步更新 [API.md](API.md)。
- 不再新增单次变更记录文档;短期变更应合并进标准文档。
+32
View File
@@ -0,0 +1,32 @@
# WordCloud 项目文档入口
本文档目录是当前项目的标准文档入口。除非某个历史文档被明确标注为“标准文档”,否则以这里列出的文档为准。
## 文档准则
- 以代码为准。文档只描述当前代码实际行为,不提前承诺未实现能力。
-`backend/core/config.py``backend/core/pipeline.py``backend/core/layout.py``backend/EfficientWordCloud/efficient_wordcloud/src/ewc_core.cpp` 为算法事实来源。
-`backend/service/app.py``backend/service/schemas.py` 为 HTTP API 事实来源。
- 变更记录只记录历史,不作为使用说明。
## 标准文档
| 文档 | 用途 |
| --- | --- |
| [PROJECT_STANDARD.md](PROJECT_STANDARD.md) | 项目结构、运行方式、输入输出、工程约定 |
| [ALGORITHM.md](ALGORITHM.md) | 词云生成算法、重复填充、权重、C++ 碰撞搜索 |
| [CONFIG.md](CONFIG.md) | 配置项、优先级、前端参数到后端配置的映射 |
| [API.md](API.md) | FastAPI 接口、请求格式、响应结构、产物下载 |
| [CANVAS_STUDIO.md](CANVAS_STUDIO.md) | 画布设计、贴纸库、词云作为贴纸、总图 SVG 导出 |
## 非标准/历史文档
以下文档可能包含历史规划、阶段性设想或已经过期的实现描述,不再作为行为依据:
- `docs/PPT-EfficientWordCloud-详细大纲-v1.0.md`
- `docs/stroke-weights-optional.md`
- `backend/docs/*`
- `backend/README.md`
- `backend/README_zh.md`
需要确认行为时,优先查标准文档;标准文档仍不清楚时,直接查代码。