如何在 gpdf 中按比例缩放图片以适配列宽?
把字节传给 c.Image 即可。gpdf 默认按列宽等比缩放。需要明确尺寸时再用 FitWidth / FitHeight。
换个说法的问题
我有一个 logo、图表或截图 —— 比如 1200×800 的 PNG —— 想放进 gpdf 的某一列里。我不想手算宽高比,不想让它被拉成椭圆,也不想让它溢出到下一列。等比缩小到合适大小,搞定,就这些。
TL;DR
c.Image(imgBytes)
最常见的情况下,这就是全部。c.Image 默认是 FitContain,会按列宽等比缩放,保持原始宽高比。如果图片本身就比列窄,gpdf 就按原尺寸画出来。
需要比整列更小的边界?加 template.FitWidth 或 template.FitHeight:
c.Image(imgBytes, template.FitWidth(document.Mm(40)))
c.Image(imgBytes, template.FitHeight(document.Mm(20)))
两个选项都保持原宽高比。只指定一个维度,另一个由 gpdf 计算。
完整示例
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
logo, err := os.ReadFile("logo.png")
if err != nil {
log.Fatal(err)
}
chart, err := os.ReadFile("chart.png")
if err != nil {
log.Fatal(err)
}
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
// logo 用窄列,固定 30mm 宽
r.Col(3, func(c *template.ColBuilder) {
c.Image(logo, template.FitWidth(document.Mm(30)))
})
// chart 用宽列,默认填满可用宽度
r.Col(9, func(c *template.ColBuilder) {
c.Image(chart)
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
3 列宽的 logo 单元格用 FitWidth(30mm),因为我们想让 logo 不论列宽多少都保持小而一致。9 列宽的 chart 单元格只用 c.Image(chart),让它充分利用列内可用宽度。两个都按比例缩放,代码里都不需要知道源图的像素数。
gpdf 中的"等比"到底指什么
总共 4 种 fit 模式,1 种是默认,覆盖大约 90% 的实际需求:
| 模式 | 行为 | 适用场景 |
|---|---|---|
FitContain (默认) | 按比例缩小适配 box,保持宽高比,可能在某一维留白 | logo、图表、截图 —— 几乎全部 |
FitCover | 按比例放大或缩小完全覆盖 box,裁掉溢出 | hero 横幅、头像方形裁剪 |
FitStretch | 完全填满 box,会扭曲宽高比 | 几乎不用,用了通常是 bug |
FitOriginal | 按 72 DPI 换算后的源像素尺寸渲染 | 按印刷分辨率制作、不能重采样的图 |
FitWidth 和 FitHeight 都是固定一维、另一维用 FitContain 算。它们是"我关心宽度"或"我关心高度"的语法糖 —— 几乎不用直接调 WithFitMode。
容易踩的坑
最常见的错误是同时给宽和高,且和源宽高比不匹配,然后抱怨图被压扁。比如这种写法:
// 除非你真的想这么做,不要写这样的代码
c.Image(img,
template.FitWidth(document.Mm(40)),
template.FitHeight(document.Mm(40)),
)
如果 PNG 是 1200×800,硬塞进 40×40 的 box,要么牺牲宽高比 (FitStretch 行为),要么牺牲一部分图像 (FitCover 行为)。默认 fit 模式是 FitContain,所以 gpdf 会保持比例、让某一维填不满 —— 图片会变成 40mm 宽、约 26mm 高,下方空着 14mm。
修法是只指定一维、信任计算。如果真的需要把非方形图裁成方的,用 FitCover,而不是两个互相矛盾的尺寸:
c.Image(img,
template.FitWidth(document.Mm(40)),
template.FitHeight(document.Mm(40)),
template.WithFitMode(document.FitCover),
)
像素数不会骗人
gpdf 在做缩放决策前会从 PNG/JPEG header 读取原始像素尺寸。所以把 4000×3000 的照片塞进 60mm 列里,并不是"在源端缩小"—— gpdf 把完整字节嵌入 PDF,重采样在 PDF 阅读器渲染时做。无论你把显示尺寸改成多少,输出 PDF 的字节数都一样。
如果文件大小比印刷质量更重要,先用 image/draw 把源图缩小,再交给 gpdf。库不会替你悄悄丢像素 —— 这个选择留给调用方。
应对布局溢出
如果列在渲染时变得比预期窄 —— 比如分页意外断开,或表格单元格收紧适配内容 —— 默认的 FitContain 会乐意把 logo 缩成邮票大小。不想这样,就设个下限:
c.Image(logo,
template.FitWidth(document.Mm(30)),
template.MinDisplayWidth(document.Mm(20)),
)
MinDisplayWidth 告诉布局引擎:如果要把图缩到 20mm 以下才能放下,那就别画在这一页,推到下一页。要么图片清晰可读,要么不画 —— 不会有"两边都不讨好"的中间态。
相关菜谱
- 如何在 gpdf 中嵌入带透明度的 PNG? —— 同一个
c.Image入口,alpha 通道那一面 - gpdf 的 12 列网格如何工作? —— "列宽"实际会算成多少
- 如何设置表格列宽? —— 当装图的 box 是表格单元格而不是行的列时
试试 gpdf
gpdf 是一个 Go 的 PDF 生成库。MIT 协议、零外部依赖、纯 Go 处理图像和字体。
go get github.com/gpdf-dev/gpdf