工作中需要提供对图片和PDF文件打上变量文字的水印,查了不少资料,调试了很多,最终开发完成。

代码如下:

package utils

import (
    "bytes"
    "fmt"
    "image"
    "image/jpeg"
    "image/png"
    "io"
    "math"
    "mime/multipart"
    "strings"

    "github.com/pdfcpu/pdfcpu/pkg/api"

    "github.com/pdfcpu/pdfcpu/pkg/pdfcpu"

    "github.com/golang/freetype/truetype"
    "golang.org/x/image/font/gofont/goregular"

    "github.com/fogleman/gg"

    "github.com/pkg/errors"
    "golang.org/x/image/bmp"
    "golang.org/x/image/webp"
)

const (
    Rgba           = 0.6 //透明度
    Radians        = -30 //旋转度
    IntervalWidth  = 24  //水印间隔宽
    IntervalHeight = 100 //水印间隔高
    FontSize       = 24
)

//scalefactor比例因子:1, 字体大小points:12, 透明度opacity:0.6, 旋转角度rotation:30
const PdfFontConfDesc = "sc:1,points:12,opacity:0.6,rot:30"

func AddTextWaterForImage(file multipart.File, fileName string, text string) ([]byte, error) {

    defer file.Close()

    //文件转换为image对象
    var imageFile image.Image
    if strings.HasSuffix(fileName, "png") {
        i, err := png.Decode(file)
        if err != nil {
            return nil, err
        }
        imageFile = i
    }
    if strings.HasSuffix(fileName, "jpeg") || strings.HasSuffix(fileName, "jpg") {
        i, err := jpeg.Decode(file)
        if err != nil {
            return nil, err
        }
        imageFile = i
    }
    if strings.HasSuffix(fileName, "bmp") {
        i, err := bmp.Decode(file)
        if err != nil {
            return nil, err
        }
        imageFile = i
    }
    if strings.HasSuffix(fileName, "webp") {
        i, err := webp.Decode(file)
        if err != nil {
            return nil, err
        }
        imageFile = i
    }
    if imageFile == nil {
        return nil, errors.New(fmt.Sprintf("invalid file suffix:%s", fileName))
    }

    //将源文件作为底层画布
    dc := gg.NewContextForImage(imageFile)

    //加载字体对象,本身加载16进制字体,无需预先加载
    font, err := truetype.Parse(goregular.TTF)
    if err != nil {
        return nil, err
    }
    //默认字体大小格式等,不设置会有默认值
    face := truetype.NewFace(font, &truetype.Options{Size: FontSize})

    //设置字体
    dc.SetFontFace(face)
    //设置颜色,透明度
    dc.SetRGBA(Rgba, Rgba, Rgba, Rgba)

    //画布的 x/y轴大小
    maxX := dc.Width()
    maxY := dc.Height()

    //设置旋转度,后两个字段代表以 画布中心为 旋转点
    dc.RotateAbout(gg.Radians(Radians), float64(maxX/2), float64(maxY/2))

    //连续水印
    textW, _ := dc.MeasureString(text) //文字宽度
    width := int(math.Ceil(textW))
    for i := -maxX / 2; i <= maxX+maxX/2; i += width + IntervalWidth {
        for j := -maxY / 2; j <= maxY+maxY/2; j += IntervalHeight {
            dc.DrawString(text, float64(i), float64(j))
        }
    }

    // 将生成的图片转换成buffer
    buffer := bytes.NewBuffer(make([]byte, 0, 512))
    err = jpeg.Encode(buffer, dc.Image(), &jpeg.Options{
        Quality: jpeg.DefaultQuality,
    })
    if err != nil {
        return nil, err
    }

    return buffer.Bytes(), nil

}

func AddTextWaterForPdf(file multipart.File, text string) ([]byte, error) {

    //文字制作为pdf水印
    //该工具会保证单行文字全部显示,所以会根据文字长度等比例强行缩放文字大小,所以需要根据传入的文字长度来设置每行的文字次数;
    count := 4
    textLen := len(text)
    switch {
    case textLen > 25 && textLen <= 40:
        count = 3
    case textLen > 40 && textLen <= 60:
        count = 2
    case textLen > 60:
        count = 1
    }
    var sb1 strings.Builder
    for i := 0; i < count; i++ {
        sb1.WriteString(text)
        if i < count-1 {
            sb1.WriteString("     ") //文字间隔
        }
    }
    //单行文字制作完成
    sb1Str := sb1.String()
    //拼接成多行文字
    var sb2 strings.Builder
    for i := 0; i < 10; i++ { //最多10行文字
        sb2.WriteString(sb1Str)
        if i < 9 {
            sb2.WriteString("\n \n \n \n \n \n \n \n \n \n")
        }
    }
    //制作成文字水印
    textWm, err := pdfcpu.ParseTextWatermarkDetails(sb2.String(), PdfFontConfDesc, true)
    if err != nil {
        return nil, err
    }
    //将水印添加到pdf文件并生成新文件
    pdfWriter := &pdfWriter{}
    var writer io.WriteSeeker = pdfWriter
    err = api.AddWatermarks(file, writer, nil, textWm, nil)
    if err != nil {
        return nil, err
    }

    return pdfWriter.buf, nil
}