图片写入pdf文件

图片写入pdf文件

需求: 要求图片自适应大小,居中写入pdf文件中,两张小图片放入一张A4纸,大图片写入一张A4纸

图片上传

// 判断路径是否存在
func Exists(path string) bool {
    _, err := os.Stat(path) //os.Stat获取文件信息
    if err != nil {
        if os.IsExist(err) {
            return true
        }
        return false
    }
    return true
}

// UploadFiles 上传多个文件
func UploadFiles(r *http.Request, key, filePath string, isCreateDir bool) ([]string, error) {
    var (
        newFilePath = filePath
        result      []string
    )
    // 创建目录
    if isCreateDir {
        ym := time.Now().Format("200601")
        newFilePath = filePath + ym + "/"
    }
    if flag := Exists(newFilePath); !flag {
        err := os.MkdirAll(newFilePath, 0755)
        if err != nil {
            return result, errors.Wrapf(xerr.NewErrCode(xerr.CreateDirFailed), "tool UploadFile create make dir failed  err:%v", err)
        }
    }
    // 根据字段名获取表单文件
    err := r.ParseMultipartForm(1024 * 1024 * 1024)
    if err != nil {
        return nil, errors.Wrapf(xerr.NewErrCode(xerr.UploadFileExceededLimit), "tool UploadFile upload image size exteed limit  err:%v", err)
    }
    files := r.MultipartForm.File[key]
    // 最多上传9张
    if len(files) > 9 {
        return nil, errors.Wrapf(xerr.NewErrCode(xerr.UploadFilesCountOverLimit), "tool UploadFile upload image count over limit  err:%v", err)
    }
    for _, file := range files {
        saveFile, err := SaveFile(file, newFilePath)
        if err != nil {
            return result, err
        }
        result = append(result, saveFile)
    }

    return result, nil
}

// SaveFile 保存单个文件
func SaveFile(file *multipart.FileHeader, filePath string) (string, error) {
    if flag := checkImageSuffix(file.Filename); !flag {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.UploadImageSuffixError), "tool SaveFile check image suffix invalid")
    }
    // 创建新的文件名
    newFileName := CreateTimeWithFileName(file)
    // 创建保存文件
    destFile, err := os.Create(filePath + newFileName)
    if err != nil {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.CreateSaveFileFailed), "tool SaveFile create dest file failed, err:%v", err)
    }
    openFile, err := file.Open()
    if err != nil {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.OpenImageFileFailed), "tool SaveFile open upload image file failed, err:%v", err)
    }
    defer openFile.Close()

    // 读取表单文件,写入保存文件
    _, err = io.Copy(destFile, openFile)
    if err != nil {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.SaveFileFailed), "tool SaveFile save to dest file failed,  err:%v", err)
    }
    // 关闭后才能修改文件名
    destFile.Close()
    newFilePath, err := GetRealFilePath(filePath + newFileName)
    if err != nil {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.ChangeImageRealSuffixFailed), "tool SaveFile change real suffix failed,  err:%v", err)
    }
    return newFilePath, nil
}

// 检测文件后缀
func checkImageSuffix(filePath string) bool {
    fileSuffix := path.Ext(filePath)
    if fileSuffix == ".jpeg" || fileSuffix == ".jpg" || fileSuffix == ".gif" || fileSuffix == ".png" {
        return true
    } else {
        return false
    }
}


// CreateTimeWithFileName 创建时间与文件名合并的文件名
func CreateTimeWithFileName(file *multipart.FileHeader) string {
    fileName := file.Filename
    // 毫秒
    now := time.Now().UnixNano() / 1e6
    fileSuffix := path.Ext(fileName)
    filePrefix := fileName[0 : len(fileName)-len(fileSuffix)]
    newFileName := fmt.Sprintf("%s_%v%s", filePrefix, now, fileSuffix)
    return newFileName
}

// 获取真实文件的路径
func GetRealFilePath(filePath string) (string, error) {
    imageType, err := GetImageRealType(filePath)
    if err != nil {
        return "", err
    }
    paths, fileName := filepath.Split(filePath)

    fileSuffix := path.Ext(fileName)
    filePrefix := fileName[0 : len(fileName)-len(fileSuffix)]
    if fileSuffix != imageType {
        newFileName := fmt.Sprintf("%s%s", filePrefix, imageType)
        newFilePath := fmt.Sprintf("%s%s", paths, newFileName)
        err := os.Rename(filePath, newFilePath)
        if err != nil {
            return "", err
        }
        return newFilePath, nil
    }
    return filePath, nil
}

// 获取文件的真实后缀
func GetImageRealType(filePath string) (string, error) {
    file, err := os.Open(filePath)

    if err != nil {
        return "", err
    }
    defer file.Close()
    buff := make([]byte, 512)
    _, err = file.Read(buff)

    if err != nil {
        return "", err
    }

    filetype := http.DetectContentType(buff)

    switch filetype {
    case "image/jpeg", "image/jpg":
        return ".jpg", nil

    case "image/gif":
        return ".gif", nil

    case "image/png":
        return ".png", nil
    default:
        return "", errors.New("文件不是图片类型")
    }
}

写入PDF

首先导入第三方库

go get github.com/jung-kurt/gofpdf

其次,把上传的图片写入到pdf

// 写入pdf
func (l *ImageWritePDFLogic) scanImageWritePdf(filePaths []string) (string, error) {
    if len(filePaths) == 0 {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.ScanFilePathsIsEmpty), "rpc scanImageWritePdf upload files is empty, data:%+v", filePaths)
    }
    saveMd5FileName := tool.Md5(filePaths[0]) + ".pdf"
    saveMd5FilePath := path.Dir(filePaths[0]) + "/" + saveMd5FileName
    var opt gofpdf.ImageOptions
    pdf := gofpdf.New("P", "mm", "A4", "")
    // 字体路径
    fontPath := l.svcCtx.Config.Upload.FontPath
    //fontFullPath := fontPath + "NotoSansSC-Regular.ttf"
    fontFullPath := fontPath + "simfang.ttf"
    if ok := tool.Exists(fontFullPath); !ok {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.FontFileNotExists), "rpc scanImageWritePdf font path is not Exists, data:%+v", fontFullPath)
    }
    // 针对linux 系统字体问题
    fontBytes, _ := ioutil.ReadFile(fontFullPath)
    pdf.AddUTF8FontFromBytes("simfang", "", fontBytes)
    // windows 适用,但是linux 不适用,注释掉
    //pdf.AddUTF8Font("simfang", "", fontFullPath)
    pdf.SetFont("simfang", "", 11)
    pdf.SetX(60)
    // 图片类型
    //opt.ImageType = "png"

    //设置页脚
    pdf.SetFooterFunc(func() {
        pdf.SetY(-10)
        pdf.CellFormat(
            0, 10,
            fmt.Sprintf("当前第 %d 页,共 {nb} 页", pdf.PageNo()), //字符串中的 {nb}。大括号是可以省的,但不建议这么做
            "", 0, "C", false, 0, "",
        )
    })
    //给个空字符串就会去替换默认的 "{nb}"。
    //如果这里指定了特别的字符串,那么SetFooterFunc() 中的 "nb" 也必须换成这个特别的字符串
    pdf.AliasNbPages("")

    // 每张纸最多放两张图片,  1<=i<=2
    i := 1
    // 判断图片大小
    for _, filePath := range filePaths {
        // 判断文件是否存在
        if ok := tool.Exists(filePath); !ok {
            return "", errors.Wrapf(xerr.NewErrCode(xerr.FindUploadFileFailed), "rpc scanImageWritePdf file upload is not Exists, data:%+v", filePath)
        }

        // 重置图片大小
        twh, err := tool.MakeThumbnailWeightHeight(filePath)
        if err != nil {
            return "", errors.Wrapf(xerr.NewErrCode(xerr.ThumbnailImageFailed), "rpc scanImageWritePdf make image width :%+v height: %+v, err:%v", twh.Width, twh.Height, err)
        }
        // 获取图片的位置
        a4p := tool.GetImagePositionWithA4(twh, i)
        // 单张
        if twh.Single {
            logx.Infof("单张存放, i:%v", i)
            pdf.AddPage()
            // 图片设置
            pdf.ImageOptions(filePath, a4p.Width, a4p.Height, twh.Width, twh.Height, false, opt, 0, "")
            i = 1
        } else {
            // 两张图片放一张A4
            // 放第一张图片,则新建纸张
            if i == 1 {
                logx.Infof("两张存放 pdf.AddPage(), i:%v", i)
                pdf.AddPage()
                i++ // 第二个位置
            } else {
                logx.Infof("两张存放, i:%v", i)
                i-- // 第二个位置,减一,变成第一个位置
            }

            // 图片设置
            pdf.ImageOptions(filePath, a4p.Width, a4p.Height, twh.Width, twh.Height, false, opt, 0, "")
        }

    }

    // 保存pdf文件
    if err := pdf.OutputFileAndClose(saveMd5FilePath); err != nil {
        return "", errors.Wrapf(xerr.NewErrCode(xerr.ImageWritePDFFailed), "rpc scanImageWritePdf save to pdf file failed, err:%v, data:%+v", err, saveMd5FilePath)
    }

    ym := time.Now().Format("200601")
    url := l.svcCtx.Config.Upload.Url + saveMd5FileName
    if strings.Contains(filePaths[0], ym) {
        url = l.svcCtx.Config.Upload.Url + ym + "/" + saveMd5FileName
    }
    return url, nil
}

图片缩放和位置定位

// 定义A4纸张最大的大小
const DEFAULT_MAX_WIDTH float64 = 210
const DEFAULT_MAX_HEIGHT float64 = 297

type ThumbnailWeightHeight struct {
    Width  float64
    Height float64
    Single bool
}

// MakeThumbnailWeightHeight 重置图片的大小
//  1 英寸 = 2.54 厘米,分辨率 = 96 像素/英寸 = 96 像素/2.54 厘米,因此 1 像素 = 2.54 厘米/96 = 0.02645833333 厘米。
// A4 大小 210 x 297 cm
func MakeThumbnailWeightHeight(imagePath string) (*ThumbnailWeightHeight, error) {
    var twh = new(ThumbnailWeightHeight)
    file, _ := os.Open(imagePath)
    defer file.Close()

    img, _, err := image.Decode(file)
    if err != nil {
        return nil, errors.Wrapf(xerr.NewErrCode(xerr.GetImageSizeFailed), "utils MakeThumbnailWeightHeight, data:%+v", imagePath)
    }

    b := img.Bounds()
    twh.Width = float64(b.Max.X)  // 图片的宽度, 像素
    twh.Height = float64(b.Max.Y) // 图片的高度, 像素
    twh.Single = false            // 一张A4纸可以放置两张图片
    //logx.Infof("图片宽度:%v, 高度:%v", twh.Width, twh.Height)

    // 计算出图片的cm
    twh.Width = math.Round(twh.Width * 0.2645833333)
    twh.Height = math.Round(twh.Height * 0.2645833333)
    //logx.Infof("计算图片大小cm,图片宽度:%v, 高度:%v", twh.Width, twh.Height)

    // 大于限制,重置大小
    for twh.Width >= DEFAULT_MAX_WIDTH || twh.Height >= DEFAULT_MAX_HEIGHT {
        twh.Width /= 2
        twh.Height /= 2
        //logx.Infof("重置后,图片宽度:%v, 高度:%v", twh.Width, twh.Height)
    }

    // 高度大于等于A4高度的一半,则只能放置一张图片
    if twh.Height >= DEFAULT_MAX_HEIGHT/2 {
        twh.Single = true
    }

    return twh, nil
}


type A4PositionWightHeight struct {
    Width  float64
    Height float64
}

// GetImagePositionWithA4 获取图片在A4纸的位置
func GetImagePositionWithA4(twh *ThumbnailWeightHeight, i int) *A4PositionWightHeight {
    var a4p = new(A4PositionWightHeight)
    a4p.Width = (DEFAULT_MAX_WIDTH / 2) - (twh.Width / 2)
    // 单张
    if twh.Single {
        a4p.Height = (DEFAULT_MAX_HEIGHT - twh.Height) / 2
    } else {
        // 第一张的高度位置
        if i == 1 {
            a4p.Height = ((DEFAULT_MAX_HEIGHT / 2) / 2) - (twh.Height / 2)
        } else {
            // 第二张的高度位置
            a4p.Height = (DEFAULT_MAX_HEIGHT / 2) + ((DEFAULT_MAX_HEIGHT / 2) / 2) - (twh.Height / 2)
        }
    }

    // logx.Infof("计算图片大小cm,图片宽度:%v, 高度1:%v, 高度2:%v", a4p.Width, a4p.FirstHeight, a4p.SecondHeight)
    return a4p
}

测试

需要把上传的目录支持nginx能访问, 这样使用域名可以访问到

curl --location --request POST --X POST 'http://localhost/look/image_share' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--form 'image=@/Users/charlie/Downloads/12.png' \
--form 'image=@/Users/charlie/Downloads/11.png' \
--form 'image=@/Users/charlie/Downloads/13.png' \
--form 'image=@/Users/charlie/Downloads/14.png'


{
    "code": 200,
    "msg": "OK",
    "data": {
        "url": "http://localhsot/upload/202206/6d6d85f74723030d0113e64c7235d4d4.pdf"
    }
}

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!