docs(memory): 分享卡复验结论(着墨偏差实测、二维码静默区回归、4px/模块上限)

This commit is contained in:
tamakyi
2026-10-22 23:24:32 +08:00
parent b35c2a8f45
commit 9fb003cc39
3 changed files with 54 additions and 1 deletions
+14
View File
@@ -398,4 +398,18 @@
(module path 写成 NekoWheel/NekoBox、没 COPY static、golang:1.20 低于 go.mod 要求的 1.27.3),
需确认 dev 是否走容器部署。
**用户放好字体后复验(200 OK)**
- 实测着墨居中偏差:链接两行 −0.5 / 0.0px、提示语 −1.0px、底部水印 480.0(原偏 9.8px)——字宽测量修复确认生效。
- 单题卡要「已回答 + 非私密」才出图:提交的提问必须先回答,否则 403/302。
探针脚本 `.workbuddy/local/qcard_probe.py`(提问 → 登录 → 回答 → 拉图 → 删除,不入库)。
- **抓到上一版自己引入的回归**:`generateQR` 把 4 模块静默区画进了图里,49 模块挤进 165px
→ 符号只有 111px(3px/模块),比改造前的 135px 还小。960 卡缩到 480 宽时模块 1.5px,
gozxing 直接解不出。
修法(`b35c2a8`):`qr.DisableBorder = true`,图像只出符号,静默区交给白卡;
调用方按实际符号尺寸居中排版;单题卡 `QQRSize` 128 → 150(37 模块拿到 4px);卡内间距收到
24/20/20/16。profile 与单题卡二维码均 111 → 148px,gozxing 在 960 原图与 75% 缩放图上都能解出正确 URL。
- 结论性数字:**960×540 下 4px/模块是当前上限(profile 卡 37 模块 @148px,用满 90%)**;
缩到 480 宽仍解不出 → 真要抗微信二次压缩只能上 2x 渲染(1920×1080),1C1G 需实测 CPU。
- `dev/overlay.json` 原来指向 /tmp 的桩文件(早已失效),改成指向仓库内 `dev/s3stub/s3_stub.go`。
+1 -1
View File
@@ -2,7 +2,7 @@
明细已拆分到 `topics/*.md`(按需 Read 全文):
`coop` 协作 / `build` 构建测试 / `embed` 行尾·模板·embed / `review` 审核处置举报 /
`gorm` GORM / `style` 前端样式 / `theme` 主题 / `builder` 主题生成器 / `misc` 其它约定。
`gorm` GORM / `style` 前端样式 / `theme` 主题 / `builder` 主题生成器 / `share` 分享卡 / `misc` 其它约定。
## 协作
+39
View File
@@ -0,0 +1,39 @@
# 分享卡(share card)
两处入口都在 `route/question/share.go`:`ShareCard`(箱子卡,960×540)、`QuestionShareCard`(单题卡)。
单题卡出图条件:**已回答 + 非私密 + 无站方处置**(`db.Restrictions.ActiveLevel` 非空即拒绝,fail-closed)。
## 文字排版
- 居中/限宽**必须**用 `measureTextWidth`(逐字 `HMetric().AdvanceWidth` + `Kern`,`scale = fontSize*64`),
它与 `DrawString` 返回笔位逐位相等。绝不要用「中文 1em / 其余 em 系数」估算:URL 里的 `: / . _ -`
只有 0.25~0.35em,整句会虚高 55%,居中直接偏 40px。
- 链接展示走 `displayURL`(去 `https://`)→ `layoutLink`(单行 15→13px,放不下按 host/path 两行 13→11px,
host 超宽才中间省略,字号下限 `LinkMinSize`)→ `ellipsizeMiddle`。限宽恒定 `QRCardW-40`。
## 二维码(`generateQR`)
- **图像只输出符号**:`qr.DisableBorder = true`,静默区由承载它的白色卡片提供。
把 4 模块静默区画进图里 + 再居中留白,会让 165px 目标只剩 111px(3px/模块)——这是曾经的回归。
- `perModule = size / modules` 向下取整,符号 = `modules*perModule`;**调用方必须用 `qr.Bounds().Dx()`
排版与居中**,不能假设等于传入的 size。
- 单题卡 `QQRSize = 150`(37 模块 → 4px);箱子卡 `QRCodeSize = 165`(37 模块 → 148px)。
- 卡内垂直间距在 4px 栅格上:`24 / 17 / 20 / 1 / 20 / 16`,总高按链接行数算,整块垂直居中。
## 实测上限
960 宽下 4px/模块是当前结构的上限;缩到 480 宽(微信/QQ 常见展示尺寸)模块只剩 2px,
gozxing 解不出。要真正抗二次压缩只能 2x 渲染(1920×1080),1C1G 要先测 CPU。
## 验证手法
- 守门测试 `route/question/share_layout_test.go`:测量=笔位、链接限宽、按着墨边界判居中 ±3px、
二维码 `width == modules×perModule` 且吃掉目标尺寸 ≥80%、头像不在边缘裁切。改完都做变异验证。
- `TEST_PREVIEW=1 go test ./route/question/ -run TestShareCardLinkPreview` 出 270×315 的卡区域 PNG。
- 图片实测:PIL 数着墨 bbox(居中偏差 / 二维码宽度 / 各段间距);解码用
`github.com/makiuchi-d/gozxing` 起个临时 module 跑(`_ "image/jpeg"` 和 `_ "image/png"` 都要 import)。
- 单题卡要真实出图:`.workbuddy/local/qcard_probe.py`(发提问 → 登录 → 回答 → 拉图 → 删除,不入库)。
## 缓存
进程内 TTL 1 小时 + 响应头 `Cache-Control: public, max-age=300`。改完要复验:重启(清进程内缓存)+ URL 加随机参数。