Go使用gofpdf修改PDF时页面分辨率重复翻倍问题求助
问题分析与解决方案
核心问题原因
- ReadSeeker指针位置错误:复用同一个
io.ReadSeeker导入多页时,第一次读取后指针移到文件末尾,后续页面导入失败,导致页面尺寸计算异常。 - PDF初始化绑定固定尺寸:初始创建PDF时指定了"A4"固定尺寸,与原PDF页面尺寸冲突,触发隐式缩放。
- 二维码尺寸设置过大:插入二维码时使用200mm的宽高(远超A4页面210mm的宽度),导致页面被迫缩放适配,重复操作时二维码相对尺寸持续缩小。
具体修复步骤
- 重置ReadSeeker指针:每次导入页面前将文件指针移到开头,确保能正确读取每一页内容。
- 动态初始化PDF:创建PDF实例时不指定固定页面尺寸,改用空字符串,避免与原PDF尺寸冲突。
- 调整二维码与字体尺寸:将二维码尺寸改为合理值(如20mm),字体大小调至适配页面的范围,避免超出页面导致缩放。
- 清理临时文件:处理完成后自动删除临时文件,避免磁盘残留。
修改后的完整代码
// Handling of post requests in order update pdf file func (a *App) qrAddToFile(w http.ResponseWriter, r *http.Request) { var png []byte //byte array for qrcode in png // 解析表单数据 err := r.ParseMultipartForm(4096) // 调整为更合理的4MB上限 if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } qrstring := r.FormValue("qrstring") png, err := qrcode.Encode(qrstring, qrcode.Medium, 256) // 生成二维码PNG if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } pngfile := savePngToFile(png) defer os.Remove(pngfile) // 自动清理临时PNG文件 // 获取上传的PDF文件 file, fileHeader, err := r.FormFile("qrfile") if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } defer file.Close() // 保存临时PDF文件 tmpfilename := fmt.Sprintf("./%d%s", time.Now().UnixNano(), fileHeader.Filename) dst, err := os.Create(tmpfilename) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer func() { dst.Close() os.Remove(tmpfilename) // 自动清理临时PDF文件 }() _, err = io.Copy(dst, file) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // 初始化PDF导入器与PDF实例(不指定固定尺寸) imp := gofpdi.NewImporter() pdf := gofpdf.New("P", "mm", "", "") // 空尺寸参数,完全适配原PDF页面 // 获取原PDF的页面数量 rs, err := getTemplatePdf(tmpfilename) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } nrPages := imp.GetPageCountFromStream(&rs) if nrPages == 0 { http.Error(w, "invalid pdf file", http.StatusBadRequest) return } // 循环导入每一页 for i := 1; i <= nrPages; i++ { // 每次导入前重置文件指针到开头 if _, err := rs.Seek(0, io.SeekStart); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } tpl := imp.ImportPageFromStream(pdf, &rs, i, "/MediaBox") pageSizes := imp.GetPageSizes() sizeType := gofpdf.SizeType{ Wd: pageSizes[i]["/MediaBox"]["w"], Ht: pageSizes[i]["/MediaBox"]["h"], } pdf.AddPageFormat("P", sizeType) imp.UseImportedTemplate(pdf, tpl, 0, 0, sizeType.Wd, sizeType.Ht) } // 添加新页面(复用最后一页的尺寸) pageSizes := imp.GetPageSizes() lastPageSize := pageSizes[nrPages]["/MediaBox"] newPageSize := gofpdf.SizeType{ Wd: lastPageSize["w"], Ht: lastPageSize["h"], } pdf.AddPageFormat("P", newPageSize) // 插入二维码(调整为20mm宽高,避免超出页面) rdr := bytes.NewReader(png) err = pdf.RegisterImageOptionsReader(pngfile, gofpdf.ImageOptions{ImageType: "png", ReadDpi: true}, rdr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } pdf.Image(pngfile, 10, pdf.GetY()+5, 20, 20, false, "", 0, "") // 添加旁注(调整字体大小与位置) tr := pdf.UnicodeTranslatorFromDescriptor("cp1251") err = pdf.AddFont("Helvetica", "", "helvetica_1251.json") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } pdf.SetFont("Helvetica", "", 12) pdf.SetXY(35, 10) pdf.SetCellMargin(2) pdf.MultiCell(150, 12, tr(qrstring), "1", "", false) // 保存最终PDF err = pdf.Error() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } err = pdf.OutputFileAndClose("QR.pdf") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte("PDF updated successfully")) } func getTemplatePdf(fname string) (io.ReadSeeker, error) { file, err := os.Open(fname) if err != nil { return nil, err } return file, nil } // 错误检查函数 func checkErr(err error) { if err != nil { log.Fatal(err) } }
额外说明
- 重置ReadSeeker指针确保每一页都能正确导入,避免因文件指针位置错误导致的页面尺寸异常。
- 动态初始化PDF完全遵循原文件的页面参数,彻底消除固定尺寸带来的隐式缩放问题。
- 调整二维码和字体尺寸后,页面无需强制适配,重复操作时二维码大小保持一致。
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

