Files

3.8 KiB
Raw Permalink Blame History

分享卡(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 = 190(37 模块 → 185px = 5px,卡片 236×300 居中于 679,120); 箱子卡 QRCodeSize = 190(37 模块 → 185px = 5px,卡片 270×335)。
  • 单题卡的二维码卡片绘制在 drawQuestionQRCard(handler 只是调用),测试可直接出图。
  • 卡内垂直间距在 4px 栅格上:24 / 17 / 20 / 1 / 20 / 16,总高按链接行数算,整块垂直居中。

模块像素:唯一的硬指标

模块 960 720 480 320 240
4px(148px,37 模块) o o x x x
5px(185px,37 模块) o o o o x

(go-qrcode 纠错 Q、真实 55 字符链接、含内嵌头像;用 gozxing 解码,它比手机严格,可当保守下限。) → 目标 5px/模块;TestGenerateQRModuleSizeAtLeast5 锁住三种常见链接长度,改小 QRCodeSize 立刻 FAIL。

改大模块的路线排序(成本从低到高):

  1. 同画布放大二维码(已做,0 CPU)——generateQR 只 0.8ms,与尺寸几乎无关。
  2. 降纠错 Q→M:模块 37→33 只省 11%,容错 25%→15%,不划算,别单独用。
  3. 换 PNG:只保住原图,缩到 480 一样解不出,体积还是 JPEG 的 5 倍。
  4. 2x 渲染:+60~100ms(本地基准:JPEG 编码 12→48ms、背景 Lanczos 25→41ms、画布 3→13ms)。 只在「原图本身也要更精细(背景/文字抗锯齿)」时值得。

缓存命中率才是 2x 之外更值得看的地方

进程内 LRU 只有 16 条 + TTL 1h,站点箱子多时冷渲染频繁;把生成结果落盘 / 上 R2 能让「贵」只付一次。这是比 2x 更实在的优化方向。

验证手法

  • 守门测试 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 加随机参数。