40 lines
2.6 KiB
Markdown
40 lines
2.6 KiB
Markdown
# 分享卡(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 加随机参数。
|