图片写入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 协议》,转载必须注明作者和本文链接