feat(ppt): quality_check 加内容形状重叠检测
原有数值检查只覆盖越界 + 按字数估算的文本溢出,盲区是"两个都在画布内的 形状互相重叠"(文本框压图标 / 压另一文本框)。加纯数值两两包围盒重叠检测: - 只检"内容形状"(有非空文字 / 是图片)。装饰元素(无文字纯色填充:品牌条/ 分隔线/圆点/色块标签/装饰星箭头)天然排除,"文字叠在色块上"也不误报 (色块无文字 → 非内容)。要抓的是文字压文字 / 文字压图标 / 图标压图标。 - 交叠宽高均 >0.08in 且 交叠面积/较小形状面积 ≥25% 才报,滤掉边缘贴合/发丝线。 - 报告含重叠百分比 + 两形状名 + 文字头,便于定位。 零依赖、确定性、host+docker 都跑(不需 LibreOffice)。测试过:合规 deck (L2 徽章+字 / L5 标签叠 chip / L4 圆点+bullet)零告警;两文本框故意叠触发。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
07959eb738
commit
7f4712dcba
|
|
@ -24,6 +24,7 @@
|
|||
### 2026-06-04
|
||||
|
||||
- **ppt skill 版式 helper 收进可 import 的模块 + 修中文字体没真生效**:逐页生成是「每页一个 run_python」,以前 ~150 行 helper(配色常量/`add_textbox`/`apply_brand` 等)要在每页里默写一遍 —— 烧 token 且长 deck 里坐标会漂(第 7 页 `apply_brand` 跟第 2 页对不上)。抽出 `skills/ppt/scripts/pptx_helpers.py`,每页 `import pptx_helpers as P` 调用;新增 `new_presentation`/`load`(按文件实际尺寸回填画布常量,逐页进程间同步)/`add_slide`/`set_palette`(默认商务红,`spec_path=` 自动取 spec 前 3 个 hex 作主/辅/强调)入口。**字体修复**:python-pptx `font.name` 只写 `<a:latin>`,中文字形走 `<a:ea>` 槽位没设 → 「指定微软雅黑却没真生效」的根因;`set_text` 改为同时写 latin=Arial + ea/cs=微软雅黑,中英混排各命中正确字体。改 `layouts.md`(helper 块换成 import 起手 + API 速查,9 个示例全改 `P.` 调用)、`icons.md` A5 示例、`SKILL.md` 资源/阶段二。冒烟测试过:`ea` 确写入、`set_palette` 覆盖生效、quality_check 正常解析。
|
||||
- **ppt `quality_check.py` 加内容形状重叠检测**:原有数值检查只覆盖越界 + 按字数估算的文本溢出,盲区是"两个都在画布内的形状互相重叠"(文本框压图标/压另一文本框)。加纯数值两两包围盒重叠检测——**只检"内容形状"**(有非空文字 / 是图片),装饰元素(无文字纯色填充:品牌条/分隔线/圆点/色块标签/装饰星箭头)天然排除,"文字叠在色块上"也不误报(色块无文字)。交叠宽高均 >0.08in 且 交叠面积/较小形状面积 ≥25% 才报,滤掉边缘贴合。测试过:合规 deck(L2 徽章+字 / L5 标签叠 chip / L4 圆点+bullet)零告警、两文本框故意叠触发并报百分比+形状名。零依赖、确定性、host+docker 都跑。
|
||||
- **ppt `quality_check.py` 配色检查纳入形状填充色 + 改按三色制判定**:原来只数 `run.font.color`(文字色),品牌条/徽章/圆点/标签/底块的**填充色全漏**——而这些恰是最易跑偏处。加 `_shape_fill_hex`(取纯色实心填充,主题色/非实心挡掉)并入 `seen_colors`。同时把粗阈值「≤5 色」改成贴合三色制的「非灰阶色 ≤3」:`_is_neutral`(R/G/B 极差 ≤12 视为灰/黑/白)把中性色排除——否则一旦计入填充,合规商务红 deck(INK/GREY/HAIRLINE/BG/WHITE+3 红)轻松超 6 狂报假阳;spec 比对也只比非灰阶色。测试过:合规红 deck 无配色 warning、塞 4+ 彩色触发、ACCENT 强调线填充被正确捕获。
|
||||
- **前端顶栏展示用户已用存储**:后端已有 `user_disk_usage` 表(后台 15min 扫描落库),但无对外查询口。加 `GET /v1/user/storage`(`Depends(require_user)`),返 `{bytes_used, file_count, limit_bytes, scanned_at}`,`limit_bytes` 由 `parse_bytes(quotas.disk_bytes_per_user)` 得(≤0/None=不限)。`disk_quota.get_user_usage` 扩为返 `(bytes,count,scanned_at)` 三元组(复用而非新开函数,顺手改唯一调用方 `check_disk_quota` 解包)。前端 `dev.html` 右侧「文件」面板底部钉一条进度条+文字指示器(`#pane-right` 改 flex 列让 `#file-list` 独占滚动、存储条钉底;`loadStorage()` 在 `enterApp` 拉一次;不限额时只显已用、隐进度条;超额变红;hover 显文件数+统计时间)。
|
||||
- **sandbox 容器 env 收编到一处 + shell 也注入(修两个只读 rootfs 副作用)**:① `PYTHONPATH=/sandbox:/workspace` 原先只 `run_python` 注入,shell 里 `python -c "from skills..."` 撞 ModuleNotFoundError;② `--read-only` rootfs 下 `/home/zcbot` 不可写,matplotlib/fontconfig 往 `~/.config`/`~/.cache` 写缓存刷 "Read-only file system" / "No writable cache" 噪音。改:`executor_docker.py` 抽 `_CONTAINER_ENV = {PYTHONPATH, HOME=/tmp}`,shell/run_python/fs 三路共用(`-e` 确定性覆盖)—— `HOME=/tmp` 一刀让缓存落 tmpfs(matplotlib→/tmp/.config、fontconfig→/tmp/.cache),不用逐个 MPLCONFIGDIR/XDG_CACHE_HOME。纯代码改,重启 web 生效,免重建镜像。
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
- 没有 untitled / output / placeholder 等占位文件名
|
||||
- **形状不越出画布边界** (left+width / top+height 超界即报)
|
||||
- **textbox 文本估算行数 > 框高度** —— 推断溢出
|
||||
- **内容形状互相重叠** (文字压文字 / 文字压图标 / 图标压图标;装饰填充不计)
|
||||
|
||||
退出码:
|
||||
0 = 全通过
|
||||
|
|
@ -31,11 +32,20 @@ try:
|
|||
from pptx import Presentation
|
||||
from pptx.util import Pt
|
||||
from pptx.enum.dml import MSO_FILL, MSO_COLOR_TYPE
|
||||
from pptx.enum.shapes import MSO_SHAPE_TYPE
|
||||
except ImportError:
|
||||
print("[fatal] pip install python-pptx", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
# ---- 重叠检测参数 ----
|
||||
# 只检"内容形状"(有文字 / 图片)两两重叠 —— 装饰形状(无文字纯色填充:品牌条/分隔线/
|
||||
# 圆点/色块标签/装饰星箭头)天然不算内容,不参与;"文字叠在色块上"也不会误报(色块无
|
||||
# 文字)。要抓的是文字压文字 / 文字压图标 / 图标压图标这类真缺陷。
|
||||
_OVERLAP_MIN_DIM = 0.08 # in:交叠的宽和高都需超过此值(滤掉边缘贴合/发丝线)
|
||||
_OVERLAP_MIN_RATIO = 0.25 # 交叠面积 / 较小形状面积 超过此比例才算"压住"
|
||||
|
||||
|
||||
# ---- 颜色辅助 ----
|
||||
|
||||
def _is_neutral(hex6: str) -> bool:
|
||||
|
|
@ -124,6 +134,7 @@ def check_pptx(path: Path, spec: dict) -> tuple[list, list]:
|
|||
title_text = None
|
||||
bullet_count = 0
|
||||
small_font_count = 0
|
||||
content_shapes: list = [] # (l, t, w, h, label, head) — 有文字 / 图片的形状
|
||||
|
||||
for s_i, shape in enumerate(slide.shapes):
|
||||
# ---- 形状越界检查 (任何 shape) ----
|
||||
|
|
@ -163,6 +174,20 @@ def check_pptx(path: Path, spec: dict) -> tuple[list, list]:
|
|||
if fill_hex:
|
||||
seen_colors.add(fill_hex)
|
||||
|
||||
# ---- 收集"内容形状"供重叠检测 (有文字 / 图片) ----
|
||||
try:
|
||||
is_pic = shape.shape_type == MSO_SHAPE_TYPE.PICTURE
|
||||
except (AttributeError, ValueError):
|
||||
is_pic = False
|
||||
head = ""
|
||||
if shape.has_text_frame:
|
||||
head = (shape.text_frame.text or "").strip()
|
||||
if (head or is_pic) and w_in > 0.05 and h_in > 0.05:
|
||||
content_shapes.append(
|
||||
(left_in, top_in, w_in, h_in, shape_label,
|
||||
head[:18] if head else "[图片]")
|
||||
)
|
||||
|
||||
if not shape.has_text_frame:
|
||||
continue
|
||||
tf = shape.text_frame
|
||||
|
|
@ -243,6 +268,25 @@ def check_pptx(path: Path, spec: dict) -> tuple[list, list]:
|
|||
f"第 {idx} 页有 {small_font_count} 处字号 < 14pt,投影看不清"
|
||||
)
|
||||
|
||||
# ---- 内容形状两两重叠 (文字压文字 / 文字压图标 / 图标压图标) ----
|
||||
for i in range(len(content_shapes)):
|
||||
ax, ay, aw, ah, alab, ahead = content_shapes[i]
|
||||
for j in range(i + 1, len(content_shapes)):
|
||||
bx, by, bw, bh, blab, bhead = content_shapes[j]
|
||||
ix = min(ax + aw, bx + bw) - max(ax, bx)
|
||||
iy = min(ay + ah, by + bh) - max(ay, by)
|
||||
if ix <= _OVERLAP_MIN_DIM or iy <= _OVERLAP_MIN_DIM:
|
||||
continue
|
||||
min_area = min(aw * ah, bw * bh)
|
||||
if min_area <= 0:
|
||||
continue
|
||||
ratio = (ix * iy) / min_area
|
||||
if ratio >= _OVERLAP_MIN_RATIO:
|
||||
warnings.append(
|
||||
f"第 {idx} 页 内容重叠 {ratio * 100:.0f}%: "
|
||||
f'{alab}("{ahead}") × {blab}("{bhead}")'
|
||||
)
|
||||
|
||||
# 三色制按"非灰阶色"判定:灰/黑/白不计 (design_principles §2「其他全部用灰阶」)
|
||||
chromatic = {c for c in seen_colors if not _is_neutral(c)}
|
||||
if len(chromatic) > 3:
|
||||
|
|
|
|||
Loading…
Reference in New Issue