You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go使用gofpdf修改PDF时页面分辨率重复翻倍问题求助

问题分析与解决方案

核心问题原因

  1. ReadSeeker指针位置错误:复用同一个io.ReadSeeker导入多页时,第一次读取后指针移到文件末尾,后续页面导入失败,导致页面尺寸计算异常。
  2. PDF初始化绑定固定尺寸:初始创建PDF时指定了"A4"固定尺寸,与原PDF页面尺寸冲突,触发隐式缩放。
  3. 二维码尺寸设置过大:插入二维码时使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 11:39:54