From 9fb003cc39788bdf8b62d3a578b2fbbecad59cef Mon Sep 17 00:00:00 2001 From: tamakyi Date: Thu, 22 Oct 2026 23:24:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(memory):=20=E5=88=86=E4=BA=AB=E5=8D=A1?= =?UTF-8?q?=E5=A4=8D=E9=AA=8C=E7=BB=93=E8=AE=BA=EF=BC=88=E7=9D=80=E5=A2=A8?= =?UTF-8?q?=E5=81=8F=E5=B7=AE=E5=AE=9E=E6=B5=8B=E3=80=81=E4=BA=8C=E7=BB=B4?= =?UTF-8?q?=E7=A0=81=E9=9D=99=E9=BB=98=E5=8C=BA=E5=9B=9E=E5=BD=92=E3=80=81?= =?UTF-8?q?4px/=E6=A8=A1=E5=9D=97=E4=B8=8A=E9=99=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .workbuddy/memory/2026-10-22.md | 14 +++++++++++ .workbuddy/memory/MEMORY.md | 2 +- .workbuddy/memory/topics/share.md | 39 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .workbuddy/memory/topics/share.md diff --git a/.workbuddy/memory/2026-10-22.md b/.workbuddy/memory/2026-10-22.md index 869e1e3..f7fe8c4 100644 --- a/.workbuddy/memory/2026-10-22.md +++ b/.workbuddy/memory/2026-10-22.md @@ -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`。 + diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 52db245..4767cb3 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -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` 其它约定。 ## 协作 diff --git a/.workbuddy/memory/topics/share.md b/.workbuddy/memory/topics/share.md new file mode 100644 index 0000000..83135e3 --- /dev/null +++ b/.workbuddy/memory/topics/share.md @@ -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 加随机参数。