圖片寫入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 協議》,轉載必須註明作者和本文連結