gofpdf 学习笔记

  • 一、安装 gofpdf
    • 1.1 初始化 go module
    • 1.2 在 go 文件中键入代码
    • 1.3 编译运行这个 go 文件
    • 1.4 检查结果
  • 二、极简入门
    • 2.1 写一句英文短句
    • 2.2 添加一张图片
      • 2.2.1 官方文档对于 `ImageOptions` 类型的解释
    • 2.3 显示中文
      • 2.3.1 注意事项
    • 2.4 生成加密的 PDF
    • 2.5 PDF 中增加链接、HTML
  • 三、跟着官方文档的学习笔记
    • 3.1 生成页眉、页脚、分页
    • 3.2 多行文字段落、行对齐、分页
      • 3.2.1 小结1:`CellFormat()` 第 5 个参数 `ln`
        • 3.2.1.1 先略微修改一下代码
        • 3.2.1.2 官方文档对 `ln` 的描述
        • 3.2.1.3 个人总结:这个参数就是指定下一行的位置
        • 3.2.1.4 个人认为官方文档的一句话有错误
    • 3.3 生成多列文档
      • 3.3.1 小结1:印刷每段文字之前,最好设置一下字体
      • 3.3.2 小结2:建议将所有 `isUTF8` 属性,赋值为 `true`
        • 3.3.2.1 `isUTF8` 参数简介
        • 3.3.2.2 演示两者的不同结果
    • 3.4 制作表格
    • 3.5 设置表格样式
    • 3.6 添加链接、简单的 HTML
    • 3.7 使用第三方字体
      • 3.7.1 注意事项1:json 文件中 calligra.z 的路径问题
        • 3.7.1.1 目录结构
        • 3.7.1.2 修改
        • 3.7.1.3 路径是参照 `main.go` 文件来写的
    • 3.8 使用 Write() 系列函数设置对齐文本
    • 3.9 文档中插入多张图片
      • 3.9.1 最终图片的呈现计算方式是以 dpi 为计量单位
        • 3.9.1.1 影响最终 dpi 渲染的 3 个因素
        • 3.9.1.2 `Image()` 函数中,`w` 和 `h` 参数的描述
        • 3.9.1.3 图片像素不变,变的只是 dpi 渲染方式
    • 3.10 影响图像水平放置的参数
      • 3.10.1 `AllowNegativePosition` 属性的说明
      • 3.10.2 两个 `ImageOptions` 字面量不要搞错了
        • 3.10.2.1 两个字面量的区别
          • 3.10.2.1.1 用途
          • 3.10.2.1.2 `pdf.ImageOptions()` 是方法
          • 3.10.2.1.3 `gofpdf.ImageOptions` 是属性
        • 3.10.2.2 两个字面量的共同点
      • 3.10.3 将 `AllowNegativePosition` 属性设置为 `falase`
    • 3.11 从 `io.Reader` 中加载图像,并注册它
      • 3.11.1 注册图像的优势
    • 3.12 制作相框样式的 pdf
    • 3.13 gocov 工具报告
    • 3.14 透明度的设置
    • 3.15 生成书签
      • 3.15.1 `pdf.Bookmark()` 方法
    • 3.16 pdf 添加其他的 pdf
      • 3.16.1 需求
      • 3.16.2 准备工作
      • 3.16.3 键入代码
      • 3.16.4 目录结构
    • 3.17 图片旋转
      • 3.17.1 首先看下效果
      • 3.17.2 `pdfg.TransformRotate()` 方法的描述
  • 一些值得注意的点,以及遇到的坑
    • 1 Windows下最好别用记事本
  • 参考文献

一、安装 gofpdf

我个人习惯的安装方式如下:

1.1 初始化 go module

1.新建一个文件夹,并进入该文件夹中
2.调出命令行窗口
3.在命令行窗口中键入命令:go mod init url/groupName/projectName

1.2 在 go 文件中键入代码

在该文件夹中,新建一个 .go 文件。键入下列代码:

package mainimport ("github.com/jung-kurt/gofpdf"
)func main() {pdf := gofpdf.New("P", "mm", "A4", "")pdf.AddPage()if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

保存该文件。

1.3 编译运行这个 go 文件

在该文件夹中,继续调出命令行窗口,键入命令:go run fileName.go
既能看到 go 系统会自动去 gofpdf 官方代码仓 拉取代码。
注意:请提前解决代理问题,否则几乎是不可能下载成功的。

1.4 检查结果

该文件夹下出现了 “1.pdf” 这个文件,打开这个 pdf 文件查看下。能够正常打开,说明这个第三方库安装成功,并能正常使用了。

二、极简入门

2.1 写一句英文短句

import ("github.com/jung-kurt/gofpdf"
)func main() {//设置页面参数pdf := gofpdf.New("P", "mm", "A4", "")//添加一页pdf.AddPage()//写文字内容之前,必须先要设置好字体pdf.SetFont("Arial", "B", 16)//CellFormat: 表格显示样式设置//CellFormat(width, height, text, border, position after, align, fill, link, linkStr)pdf.CellFormat(0, 0, "Welcome to golangcode.com", "0", 0, "LM", false, 0, "")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

2.2 添加一张图片

import ("github.com/jung-kurt/gofpdf"
)func main() {//设置页面参数pdf := gofpdf.New("P", "mm", "A4", "")//添加一页pdf.AddPage()//将图片放入到 pdf 文档中//ImageOptions(src, x, y, width, height, flow, options, link, linkStr)pdf.ImageOptions("1.jpg",0, 0,0, 0,false,gofpdf.ImageOptions{ImageType: "jpg", ReadDpi: false},0,"",)if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

2.2.1 官方文档对于 ImageOptions 类型的解释

// ImageOptions provides a place to hang any options we want to use while
// parsing an image.
//
// ImageType's possible values are (case insensitive):
// "JPG", "JPEG", "PNG" and "GIF". If empty, the type is inferred from
// the file extension.
//
// ReadDpi defines whether to attempt to automatically read the image
// dpi information from the image file. Normally, this should be set
// to true (understanding that not all images will have this info
// available). However, for backwards compatibility with previous
// versions of the API, it defaults to false.
//
// AllowNegativePosition can be set to true in order to prevent the default
// coercion of negative x values to the current x position.
type ImageOptions struct {ImageType             stringReadDpi               boolAllowNegativePosition bool
}

大意:
ImageType:只支持:JPG、JPEG、PNG、GIF,这个四个格式。无所谓字符串大小写。如果为空,则根据文件扩展名自行推导。
ReadDpi:是否尝试自动从图像文件读取图像 dpi 信息。 通常,应将其设置为 true(请注意,并非所有图像都会提供此信息)。 但是,为了向后兼容该API的早期版本,它默认为 false个人笔记注:并非所有图像都提供 dpi,就凭这一项,就应该设置为 false

2.3 显示中文

func main() {pdf := gofpdf.New("P", "mm", "A4", "")pdf.AddPage()//将字体加载进来//AddUTF8Font("给字体起个别名", "", "fontPath")pdf.AddUTF8Font("simfang", "", "simfang.ttf")//使用这个字体//SetFont("字体的别名", "", size)pdf.SetFont("simfang", "", 20)pdf.Text(5, 10, "阿毛生信系统")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

2.3.1 注意事项

1.AddUTF8Font() 暂不支持 .ttc 格式的字体!
2.经实践:在 AddUTF8FontSetFont() 中,设置 styleStr 属性,好像对中文字体无效。正在寻找其他解决方案。

2.4 生成加密的 PDF

打开 PDF 时,会弹出密码框,要求用户输入该文档的密码。

func main() {pdf := gofpdf.New("P", "mm", "A4", "")pdf.AddPage()pdf.AddUTF8Font("simfang", "", "simfang.ttf")pdf.SetFont("simfang", "", 20)pdf.Text(5, 10, "阿毛生信系统")//CnProtectPrint: 表示该文档允许被打印pdf.SetProtection(gofpdf.CnProtectPrint, "abc123", "")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

2.5 PDF 中增加链接、HTML

func main() {pdf := gofpdf.New("P", "mm", "A4", "")pdf.AddPage()//先设置字体pdf.SetFont("Helvetica", "", 20)_, lineHt := pdf.GetFontSize() //获取 line heightfmt.Println(lineHt)            //7.055555555555556pdf.Write(lineHt, "To find out what's new in this tutorial, click ")pdf.SetFont("", "U", 0)link := pdf.AddLink()pdf.WriteLinkID(lineHt, "here", link)pdf.SetFont("", "", 0)//Second page: image link and basic HTML with linkpdf.AddPage()pdf.SetLink(link, 0, -1)pdf.Image("./images/1.jpg", 10, 12, 30, 0, false, "", 0, "www.google.com/ncr")pdf.SetLeftMargin(45)pdf.SetFontSize(8)htmlStr := `You can now easily print text mixing different styles: <b>bold</b>, ` +`<i>italic</i>, <u>underlined</u>, or <b><i><u>all at once</u></i></b>!<br><br>` +`<center>You can also center text.</center>` +`<right>Or align it to the right.</right>` +`You can also insert links on text, such as ` +`<a href="http://www.fpdf.org">http://blog.csdn.net/wangshubo1989?viewmode=contents</a>, or on an image: click on the logo.`html := pdf.HTMLBasicNew()html.Write(5.0, htmlStr) //也可以手动指定 line heightif err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

三、跟着官方文档的学习笔记

GitHub 仓中的这个文件:fpdf_test.go,演示了该库的所有功能。

3.1 生成页眉、页脚、分页

代码来源:fpdf_test.go#L310

func main() {pdf := gofpdf.New("P", "mm", "A4", "")pdf.SetTopMargin(30)pdf.AddUTF8Font("simfang", "", "simfang.ttf")pdf.SetFont("simfang", "", 12)//设置页眉pdf.SetHeaderFuncMode(func() {pdf.Image("./images/amaosys-logo.png", 0, 0, 0, 0, false, "", 0, "")pdf.SetY(5)pdf.Ln(10)}, true)//设置页脚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("")pdf.AddPage()for j := 0; j < 100; j++ {pdf.CellFormat(0, 10, fmt.Sprintf("正在打印:%d", j),"", 1, "", false, 0, "",)}if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

3.2 多行文字段落、行对齐、分页

代码来源:fpdf_test.go#L342

func main() {pdf := gofpdf.New("P", "mm", "A4", "")w, h := pdf.GetPageSize()fmt.Printf("pdf size, w:%.2f, h:%.2f", w, h) //pdf size, w:210.00, h:297.00titleStr := "20000 Leagues Under the Seas"pdf.SetTitle(titleStr, false)pdf.SetAuthor("Jules Verne", false)pdf.SetHeaderFuncMode(func() {pdf.SetFont("Arial", "B", 15)wd := pdf.GetStringWidth(titleStr) + 6pdf.SetY(0.6)            //先要设置 Y,然后再设置 X。否则,会导致 X 失效pdf.SetX((210 - wd) / 2) //水平居中的算法pdf.SetDrawColor(0, 80, 180)  //frame colorpdf.SetFillColor(230, 230, 0) //background colorpdf.SetTextColor(220, 50, 50) //text colorpdf.SetLineWidth(1)pdf.CellFormat(wd, 10, titleStr, "1", 1, "CM", true, 0, "")//第 5 个参数,实际效果是:指定下一行的位置pdf.Ln(5)}, false)pdf.SetFooterFunc(func() {pdf.SetY(-15)pdf.SetFont("Arial", "I", 8)pdf.SetTextColor(128, 128, 128)pdf.CellFormat(0, 5,fmt.Sprintf("Page %d", pdf.PageNo()),"", 0, "C", false, 0, "",)})//标题chapterTitle := func(chapNum int, titleStr string) {pdf.SetFont("Arial", "", 12)pdf.SetFillColor(200, 220, 255) //background colorpdf.CellFormat(0, 6,fmt.Sprintf("Chapter %d : %s", chapNum, titleStr),"", 1, "L", true, 0, "",)pdf.Ln(2)}//主体chapterBody := func(fileStr string) {textStr, err := ioutil.ReadFile(fileStr)if err != nil {pdf.SetError(err)}pdf.SetFont("Times", "", 12)//输出对齐文本pdf.MultiCell(0, 5, string(textStr), "", "", false)pdf.Ln(-1)pdf.SetFont("", "I", 0)pdf.Cell(0, 5, "(end of excerpt)")}//印刷每一页printChapter := func(chapNum int, titleStr, fileStr string) {pdf.AddPage()chapterTitle(chapNum, titleStr)chapterBody(fileStr)}printChapter(1, "A RUNAWAY REEF", "./text/20k_c1.txt")printChapter(2, "THE PROS AND CONS", "./text/20k_c2.txt")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

3.2.1 小结1:CellFormat() 第 5 个参数 ln

pdf.CellFormat() 中的第 5 个参数 ln,其数据类型是 int

3.2.1.1 先略微修改一下代码

为了让后续的演示看起来更加清晰,这里将 3.2 的代码中,SetHeaderFuncMode() 函数片段中的最后一行 pdf.Ln(1) 注释了,也就是不采取创建空行。(其他代码可改可不改)

pdf.SetHeaderFuncMode(func() {pdf.SetFont("Arial", "B", 15)wd := pdf.GetStringWidth(titleStr) + 6pdf.SetY(0.6)            //先要设置 Y,然后再设置 X。否则,会导致 X 失效pdf.SetX((210 - wd) / 2) //水平居中的算法pdf.SetDrawColor(0, 80, 180)  //frame colorpdf.SetFillColor(230, 230, 0) //background colorpdf.SetTextColor(220, 50, 50) //text colorpdf.SetLineWidth(1)pdf.CellFormat(wd, 10, titleStr, "1", 2, "CM", true, 0, "")//pdf.Ln(1) //这行注释了}, false)

Ln(h float64) 表示:创建一个高度为 h 的空行。

3.2.1.2 官方文档对 ln 的描述
// ln indicates where the current position should go after the call. Possible
// values are 0 (to the right), 1 (to the beginning of the next line), and 2
// (below). Putting 1 is equivalent to putting 0 and calling Ln() just after.

Google 翻译:ln 指示呼叫后当前位置应该去哪里。 可能的值为 0(在右边),1(在下一行的开头)和 2(在下面)。放置 1 等同于放置 0,并在其后调用 Ln()

3.2.1.3 个人总结:这个参数就是指定下一行的位置
  • 0:表示不换行,并紧跟在这个 Cell 的右边。
    效果如下图所示:
  • 1:发生换行,并在下一行的顶头位置。
    效果如下图所示:
  • 2:发生换行,但是会在这个 Cell 的下方。
    效果如下图所示:
3.2.1.4 个人认为官方文档的一句话有错误

012,这三个值的最终效果,差别很大。从最终效果来看,我认为官方文档的这句话:

Putting 1 is equivalent to putting 0 and calling Ln() just after.

有错误,描述与实际效果不相同。

3.3 生成多列文档

代码来源:fpdf_test.go#L420

import ("fmt""github.com/jung-kurt/gofpdf""io/ioutil"
)func main() {var y0 float64var crrntCol intpdf := gofpdf.New("P", "mm", "A4", "")pdf.SetDisplayMode("fullpage", "TwoColumnLeft")//添加“仿宋”字体pdf.AddUTF8Font("simfang", "", "simfang.ttf")pdf.SetAuthor("毛莹", true)titleStr := "阿毛生信系统"pdf.SetTitle(titleStr, true)setCol := func(col int) {//根据给定的列,设置位置crrntCol = colx := 10.0 + float64(col)*65.0pdf.SetLeftMargin(x)pdf.SetX(x)}//标题chapterTitle := func(chapNum int, titleStr string) {pdf.SetFont("Arial", "", 12)pdf.SetFillColor(200, 220, 255) //background colorpdf.CellFormat(0, 6,fmt.Sprintf("Chapter %d : %s", chapNum, titleStr),"", 1, "L", true, 0, "",)pdf.Ln(2)y0 = pdf.GetY()}chapterBody := func(fileStr string) {txtBuf, err := ioutil.ReadFile(fileStr)if err != nil {panic(err)}pdf.SetFont("Times", "", 12)pdf.MultiCell(60, 5, string(txtBuf), "", "", false)pdf.SetFont("", "I", 0)pdf.Cell(0, 5, "(end of excerpt)")setCol(0) //返回第一列}printChapter := func(num int, titleStr, fileStr string) {pdf.AddPage()chapterTitle(num, titleStr)chapterBody(fileStr)}//判定是否需要分页pdf.SetAcceptPageBreakFunc(func() bool {if crrntCol < 2 {setCol(crrntCol + 1)pdf.SetY(y0)return false //继续保持在当前页}setCol(0)return true //执行分页})pdf.SetHeaderFunc(func() {pdf.SetFont("simfang", "", 15) //设置“仿宋”字体wd := pdf.GetStringWidth(titleStr) + 6pdf.SetX((210 - wd) / 2)pdf.SetDrawColor(0, 80, 180)  //frame colorpdf.SetFillColor(230, 230, 0) //background colorpdf.SetTextColor(220, 50, 50) //text colorpdf.SetLineWidth(1)pdf.CellFormat(wd, 9, titleStr, "1", 1, "C", true, 0, "")pdf.Ln(5)y0 = pdf.GetY() //保存纵坐标})pdf.SetFooterFunc(func() {pdf.SetY(-15)pdf.SetFont("Arial", "I", 8)pdf.SetTextColor(128, 128, 128)pdf.CellFormat(0, 10,fmt.Sprintf("Page %d", pdf.PageNo()),"", 0, "C", false, 0, "",)})printChapter(1, "A RUNAWAY REEF", "./text/20k_c1.txt")printChapter(2, "THE PROS AND CONS", "./text/20k_c2.txt")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

3.3.1 小结1:印刷每段文字之前,最好设置一下字体

在每一段 Cell() 或者 Text() 文字之前,都要调用 pdf.SetFont() 设置好字体,不然就会沿用上一个已设置的字体。
如果文字内容没有任何复合字符,那么用默认的字体是没有问题。但如果出现了复合字符,那么就要提前设置好一个能够支持该复合字符的字体,否则就是乱码。
比如,我有一段文字内容,里面既有中文也有英文。此时,就必须要设置好一个能够识别中文字符的字体。
演示,将上例代码中的 chapterBody 函数修改成如下(其他代码可改可不改):

chapterBody := func(fileStr string) {txtBuf, err := ioutil.ReadFile(fileStr)if err != nil {panic(err)}//统计 unicode 字符的数量runeLen := utf8.RuneCount(txtBuf)//把最终的 unicode 字符放入这个 []runeresult := make([]rune, runeLen)//读取到的内容是 []byte 类型,但内容中有中文,需要用 utf8.DecodeRune() 转换为 unicode 字符for i := 0; runeLen > 0; i++ {r, size := utf8.DecodeRune(txtBuf)result[i] = rtxtBuf = txtBuf[size:]runeLen--}//可以打印到控制台,看一下效果fmt.Println(result)fmt.Println(string(result))pdf.SetFont("simfang", "", 14)pdf.MultiCell(90, 5, string(result), "", "", false)pdf.Cell(0, 5, "(本文已结束)")setCol(0) //返回第一列}

运行效果:

备注:如果内容中有日文、德文等复合字符,就需要去找到对应的字体去支持它们。字体不支持这个复合字符,也会变成乱码。

3.3.2 小结2:建议将所有 isUTF8 属性,赋值为 true

3.3.2.1 isUTF8 参数简介

有很多涉及到设置文档属性的方法,例如:

func (f *Fpdf) SetProducer(producerStr string, isUTF8 bool)
func (f *Fpdf) SetTitle(titleStr string, isUTF8 bool)
func (f *Fpdf) SetSubject(subjectStr string, isUTF8 bool)
func (f *Fpdf) SetAuthor(authorStr string, isUTF8 bool)
func (f *Fpdf) SetKeywords(keywordsStr string, isUTF8 bool)
func (f *Fpdf) SetCreator(creatorStr string, isUTF8 bool)
func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool)

这些方法中,都有一个 isUTF8 参数,其类型为 bool。该参数指定了是否以 UTF-8 编码来处理字符串。

  • 设置为 true 时:
    字符串将以 UTF-8 编码来处理。
  • 设置为 false 时:
    字符串将以 ISO-8859-1 编码来处理。
3.3.2.2 演示两者的不同结果

我的系统是 Windows,并且已安装了 Adobe Reader。使用 Adobe Reader 打开 PDF 文件,按 Ctrl + D 组合键,查看此 PDF 文件的属性。

  • 设置为 true
    能够正常显示中文字:
  • 设置为 false
    中文就变成了乱码:

3.4 制作表格

代码来源:fpdf_test.go#L525

func loremList() []string {return []string{"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " +"tempor incididunt ut labore et dolore magna aliqua.","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +"aliquip ex ea commodo consequat.","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " +"dolore eu fugiat nulla pariatur.","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +"officia deserunt mollit anim id est laborum.",}
}func main() {const (colCount = 3colWd    = 60.0marginH  = 15.0lineHt   = 5.5cellGap  = 2.0)type cellType struct {str  stringlist [][]byteht   float64}var (cellList [colCount]cellTypecell     cellType)pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297header := [colCount]string{"Column A", "Column B", "Column C"}alignList := [colCount]string{"L", "C", "R"}strList := loremList()pdf.SetMargins(marginH, 15, marginH)pdf.SetFont("Arial", "", 14)pdf.AddPage()//设置表格第一行的样式(也就是在设置 <thead> 标签的样式)pdf.SetTextColor(224, 224, 224)pdf.SetFillColor(64, 64, 64)for colJ := 0; colJ < colCount; colJ++ {pdf.CellFormat(colWd, 10, header[colJ], "1", 0, "CM", true, 0, "")}pdf.Ln(-1)//设置每一行pdf.SetTextColor(24, 24, 24)pdf.SetFillColor(255, 255, 255)y := pdf.GetY()count := 0for rowJ := 0; rowJ < 2; rowJ++ {maxHt := lineHt//计算单元格的高度for colJ := 0; colJ < colCount; colJ++ {count++if count > len(strList) {count = 1}cell.str = strings.Join(strList[:count], " ")cell.list = pdf.SplitLines([]byte(cell.str), colWd-cellGap*2)cell.ht = float64(len(cell.list)) * lineHtif cell.ht > maxHt {maxHt = cell.ht}cellList[colJ] = cell}//循环渲染每个单元格x := marginHfor colJ := 0; colJ < colCount; colJ++ {pdf.Rect(x, y, colWd, maxHt+cellGap*2, "D")cell = cellList[colJ]cellY := y + cellGap + (maxHt-cell.ht)/2for splitJ := 0; splitJ < len(cell.list); splitJ++ {pdf.SetXY(x+cellGap, cellY)pdf.CellFormat(colWd-cellGap*2, lineHt, string(cell.list[splitJ]), "", 0, alignList[colJ], false, 0, "")cellY += lineHt}x += colWd}y += maxHt + cellGap*2}if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:

3.5 设置表格样式

代码来源:fpdf_test.go#L606

//strDelimit converts 'ABCDEFG' to, for example, 'A,BCD,EFG'
func strDelimit(str string, sepstr string, sepcount int) string {pos := len(str) - sepcountfor pos > 0 {str = str[:pos] + sepstr + str[pos:]pos = pos - sepcount}return str
}func main() {pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297type countryType struct {nameStr, capitalStr, areaStr, popStr string}countryList := make([]countryType, 0, 8)header := []string{"Country", "Capital", "Area (sq km)", "Pop. (thousands)"}loadData := func(fileStr string) {fl, err := os.Open(fileStr)if err != nil {panic(err)}defer fl.Close()scanner := bufio.NewScanner(fl)var c countryTypefor scanner.Scan() {//Austria;Vienna;83859;8075lineStr := scanner.Text()list := strings.Split(lineStr, ";")if len(list) == 4 {c.nameStr = list[0]c.capitalStr = list[1]c.areaStr = list[2]c.popStr = list[3]countryList = append(countryList, c)} else {fmt.Printf("error tokenizing %s\n", lineStr)}}if len(countryList) == 0 {fmt.Printf("error loading data from %s\n", fileStr)}}//简单的表格basicTable := func() {left := (210.0 - 4*40) / 2pdf.SetX(left)for _, str := range header {pdf.CellFormat(40, 7, str, "1", 0, "", false, 0, "")}pdf.Ln(-1)for _, c := range countryList {pdf.SetX(left)pdf.CellFormat(40, 6, c.nameStr, "1", 0, "", false, 0, "")pdf.CellFormat(40, 6, c.capitalStr, "1", 0, "", false, 0, "")pdf.CellFormat(40, 6, c.areaStr, "1", 0, "", false, 0, "")pdf.CellFormat(40, 6, c.popStr, "1", 0, "", false, 0, "")pdf.Ln(-1)}}//Better tableimprovedTable := func() {w := []float64{40.0, 35.0, 40.0, 45.0} //列宽wSum := 0.0for _, v := range w {wSum += v}left := (210 - wSum) / 2pdf.SetX(left)for j, str := range header {pdf.CellFormat(w[j], 7, str, "1", 0, "C", false, 0, "")}pdf.Ln(-1)for _, c := range countryList {pdf.SetX(left)pdf.CellFormat(w[0], 6, c.nameStr, "LR", 0, "", false, 0, "")pdf.CellFormat(w[1], 6, c.capitalStr, "LR", 0, "", false, 0, "")pdf.CellFormat(w[2], 6, strDelimit(c.areaStr, ",", 3), "LR", 0, "", false, 0, "")pdf.CellFormat(w[3], 6, strDelimit(c.popStr, ",", 3), "LR", 0, "", false, 0, "")pdf.Ln(-1)}pdf.SetX(left)pdf.CellFormat(wSum, 0, "", "T", 0, "", false, 0, "")}//Colored tablefancyTable := func() {pdf.SetFillColor(255, 0, 0)pdf.SetTextColor(255, 255, 255)pdf.SetDrawColor(128, 0, 0)pdf.SetLineWidth(.3)pdf.SetFont("", "B", 0)w := []float64{40, 35, 40, 45}wSum := 0.0for _, v := range w {wSum += v}left := (210 - wSum) / 2pdf.SetX(left)for j, str := range header {pdf.CellFormat(w[j], 7, str, "1", 0, "C", true, 0, "")}pdf.Ln(-1)pdf.SetFillColor(224, 235, 255)pdf.SetTextColor(0, 0, 0)pdf.SetFont("", "", 0)fill := falsefor _, c := range countryList {pdf.SetX(left)pdf.CellFormat(w[0], 6, c.nameStr, "LR", 0, "", fill, 0, "")pdf.CellFormat(w[1], 6, c.capitalStr, "LR", 0, "", fill, 0, "")pdf.CellFormat(w[2], 6, strDelimit(c.areaStr, ",", 3), "LR", 0, "", fill, 0, "")pdf.CellFormat(w[3], 6, strDelimit(c.popStr, ",", 3), "LR", 0, "", fill, 0, "")pdf.Ln(-1)fill = !fill}pdf.SetX(left)pdf.CellFormat(wSum, 0, "", "T", 0, "", false, 0, "")}loadData("./text/countries.txt")pdf.SetFont("Arial", "", 14)pdf.AddPage()basicTable()pdf.AddPage()improvedTable()pdf.AddPage()fancyTable()if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行效果:


3.6 添加链接、简单的 HTML

代码来源:fpdf_test.go#L744

func main() {pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297pdf.AddPage()//点击链接,跳转到当前文档中的位置pdf.SetFont("Helvetica", "", 20)_, lineHt := pdf.GetFontSize()pdf.Write(lineHt, "To find out what's new in this tutorial, click ")pdf.SetFont("", "U", 0)link := pdf.AddLink() //添加链接pdf.WriteLinkID(lineHt, "here", link) //注册这个链接//添加图片链接、基础的 HTML <a> 标签链接pdf.AddPage()pdf.SetLink(link, 0, -1) //使用刚才已注册好的链接pdf.Image("./images/logo.png", 10, 12, 30, 0, false, "", 0, "http://www.fpdf.org")pdf.SetLeftMargin(45)pdf.SetFontSize(14)_, lineHt = pdf.GetFontSize()htmlStr := `You can now easily print text mixing different styles: <b>bold</b>, ` +`<i>italic</i>, <u>underlined</u>, or <b><i><u>all at once</u></i></b>!<br><br>` +`<center>You can also center text.</center>` +`<right>Or align it to the right.</right>` +`You can also insert links on text, such as ` +`<a href="https://github.com/jung-kurt/gofpdf/blob/master/fpdf_test.go">gofpdf github link.</a>, or on an image: click on the logo.`html := pdf.HTMLBasicNew()html.Write(lineHt, htmlStr)if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行结果:

鼠标移动移动到 “here” 处,指针图标会变成手型图标,点击后会发生跳转。如下画面:

点击大象图案,或者 “gofpdf github link.”,都可以在浏览器中打开这个网站。

3.7 使用第三方字体

代码来源:fpdf_test.go#L778

func main() {pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297pdf.AddFont("Calligrapher", "", "./font/calligra.json")pdf.AddPage()pdf.SetFont("Calligrapher", "", 35)pdf.Cell(0, 10, "Enjoy new fonts with FPDF!")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行效果:

3.7.1 注意事项1:json 文件中 calligra.z 的路径问题

3.7.1.1 目录结构

这是我的目录结构:

官方的示例代码中,加载的是 calligra.json 文件,然后再去映射各个字体。
在这个 json 文件中,大概第 1250 个字符开始。有一个 File 字段,指定了另一个依赖文件的路径,其原本的值是 "calligra.z"

3.7.1.2 修改

需要将这个值改一下,改成:参照 main.go 文件的路径。最终结果如下:

3.7.1.3 路径是参照 main.go 文件来写的

calligra.json 文件存放在项目根目录的 font 文件夹中,main.go 文件启动整个程序。那么以 main.go 文件作为参照物来说:calligra.json 它存放的路径是在 main.go 目录下的 font 文件夹中。
main.go 文件的路径是 ./main.go,参照着这个路径来写 calligra.json 文件的路径,就是 ./font/calligra.z。(相对路径与绝对路径,均参照这个 main.go 文件)。
官方示例中,也是将这个 json 文件放置在项目根目录下的 font 文件夹中。但是,我在实践的时候,报了这个异常:panic: open calligra.z: The system cannot find the file specified.,无法找到这个文件。
后来点进 calligra.json 文件中,将 File 的值改成了上图中的路径值,就能成功了。改成绝对路径也可以:/font/calligra.z 。总之一句话:一切路径参照这个 main.go 文件的路径。

3.8 使用 Write() 系列函数设置对齐文本

代码来源:fpdf_test.go#L792

func main() {pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297pdf.SetLeftMargin(50.0)pdf.SetRightMargin(50.0)pdf.AddPage()pdf.SetFont("Helvetica", "", 12)pdf.WriteAligned(0, 35, "This text is the default alignment, Left", "")pdf.Ln(35)pdf.WriteAligned(0, 35, "This text is aligned Left", "L")pdf.Ln(35)pdf.WriteAligned(0, 35, "This text is aligned Center", "C")pdf.Ln(35)line := "This can by used to write justified text"leftMargin, _, rightMargin, _ := pdf.GetMargins()pageWidth, _ := pdf.GetPageSize()pageWidth -= leftMargin + rightMarginpdf.SetWordSpacing((pageWidth - pdf.GetStringWidth(line)) / float64(strings.Count(line, " ")))pdf.WriteAligned(pageWidth, 35, line, "L")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行效果:

3.9 文档中插入多张图片

代码来源:fpdf_test.go#L820

func main() {pdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297pdf.AddPage()pdf.SetFont("Arial", "", 11)pdf.Image("./images/logo.png", 10, 10, 30, 0, false, "", 0, "")pdf.Text(50, 20, "logo.png")pdf.Image("./images/logo.gif", 10, 40, 30, 0, false, "", 0, "")pdf.Text(50, 50, "logo.gif")pdf.Image("./images/logo-gray.png", 10, 70, 30, 0, false, "", 0, "")pdf.Text(50, 80, "logo-gray.png")pdf.Image("./images/logo-rgb.png", 10, 100, 30, 0, false, "", 0, "")pdf.Text(50, 110, "logo-rgb.png")pdf.Image("./images/logo.jpg", 10, 130, 30, 0, false, "", 0, "")pdf.Text(50, 140, "logo.jpg")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行效果:

3.9.1 最终图片的呈现计算方式是以 dpi 为计量单位

pdf 的内容都是印刷的方式写入到文件中,所以,所有的图片都是以 dpi 为计量单位。为的就是方便后续渲染、打印。
dpi 的描述可以参考这篇文章:link

3.9.1.1 影响最终 dpi 渲染的 3 个因素

1.图片的原始 dpi 数值。
2.Image() 函数中 wh 设置的 dpi 数值。
3.pdf 设置的页面单位(mm / cm)。

最终的 dpi 渲染值,是综合这 3 个因素而得出。

3.9.1.2 Image() 函数中,wh 参数的描述

如果 w 和 h 均为 0,则图像以 96 dpi 渲染。
如果 w 或 h 为零,则将从另一个维度计算得出,以便保持宽高比。
如果 w 和/或 h 为 -1,则将从 ImageInfoType 对象读取该尺寸的 dpi。PNG文件可以包含 dpi 信息(如果存在的话),该信息将填充在 ImageInfoType 对象中,并用于“宽度”,“高度”和“范围”计算中。否则,可以使用 SetDpi() 函数将 dpi 从默认值 72 更改。
如果 w 和 h 为其他任何负值,则它们的绝对值表示其 dpi 范围。

3.9.1.3 图片像素不变,变的只是 dpi 渲染方式

图片渲染方式是由最终的 dpi 数值所决定。
首先查看一下一张图片的属性:

pdf 页面设置单位为 mm,将 Image() 中的 w 参数设置为 100 后,得到下图效果:

使用 Adobe Reader 打开这个 pdf 文件,选中一张图片,复制它。

打开 Photoshop,然后 “新建” 一个新的 PS 文件。PS 它会读取剪切板中的内容,如果剪切板中的内容是图片,那么新建一个新的 PS 文件时,它会自动将像素调整为剪切板中图片的像素大小。
可以看到,尽管在 pdf 中,这张图片看起来很大,但图片像素根本没有发生变化:

因为在 Image() 函数中,设置了 w 的值为 100。所以,渲染 dpi 的值发生了改变。最终印刷 pdf 的时候,图片就根据了这个变大的 dpi 值进行渲染。

3.10 影响图像水平放置的参数

代码来源:fpdf_test.go#L843

func main() {var opt gofpdf.ImageOptionspdf := gofpdf.New("P", "mm", "A4", "") // 210 x 297pdf.AddPage()pdf.SetFont("Arial", "", 11)opt.ImageType = "png"pdf.ImageOptions("./images/logo.png", 10, 10, 30, 0, false, opt, 0, "")opt.AllowNegativePosition = truepdf.ImageOptions("./images/logo.png", -10, 50, 30, 0, false, opt, 0, "")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行效果:

3.10.1 AllowNegativePosition 属性的说明

AllowNegativePosition 这个属性隶属于 gofpdf.ImageOptions 结构体中。
官方文档对这个属性的描述:

AllowNegativePosition can be set to true in order to prevent the default
coercion of negative x values to the current x position.

Google 翻译后:
可以将 AllowNegativePosition 设置为 true,以防止将负 x 值默认强制转换为当前 x 位置。
实际作用:
是否允许负 x 值生效。指定这个属性为 true 后,如果图片的横坐标设置了负数,那么这张就可以溢出到横坐标(如 3.10 代码的运行结果,第二张大象图片,就是溢出了横坐标)。

3.10.2 两个 ImageOptions 字面量不要搞错了

将图片印刷进 pdf 文件的是:pdf.ImageOptions() 方法。设置图片选项的是:gofpdf.ImageOptions 属性。

3.10.2.1 两个字面量的区别
3.10.2.1.1 用途

pdf.ImageOptions() 方法是将图片印刷进 pdf 文件中,起到的是渲染的作用,真正实现让人在 pdf 中能够看到这张图片。
gofpdf.ImageOptions 属性是设置图片的一些选项。它有 3 个属性:图片格式、DPI 选项、是否允许负 x 值生效。

3.10.2.1.2 pdf.ImageOptions() 是方法

方法的本质就是:一个函数关联给了一个字面量。ImageOptions() 方法是 gofpdf.Fpdf 这个结构体的关联函数。

3.10.2.1.3 gofpdf.ImageOptions 是属性

ImageOptions 属性是:gofpdf 包下的 ImageOptions 结构体,其中包含了如下 3 个字段:

type ImageOptions struct {ImageType             stringReadDpi               boolAllowNegativePosition bool
}
3.10.2.2 两个字面量的共同点

1.都是在 gofpdf 包下。
2.都是结构体类型。

3.10.3 将 AllowNegativePosition 属性设置为 falase

AllowNegativePosition 属性设置为 falase 后,就会强制将负 x 的值,转为正 x 值。也就是负 x 值不生效了。
运行效果:

3.11 从 io.Reader 中加载图像,并注册它

代码来源:fpdf_test.go#L863

func main() {var (opt gofpdf.ImageOptionsfl  *os.Fileerr error)pdf := gofpdf.New("P", "mm", "A4", "")pdf.AddPage()pdf.SetFont("Arial", "", 11)fl, err = os.Open("./images/logo.png")if err != nil {panic(err)return}opt.ImageType = "png"opt.AllowNegativePosition = true//从 fl 中加载文件,并注册图像。注册名为 logo(类似于注册一个事件,名为 logo)_ = pdf.RegisterImageOptionsReader("logo", opt, fl)fl.Close() //注册完,就可以关闭这个文件了for x := -20.0; x <= 40.0; x += 5 {//使用这个名为 logo 的注册图像(类似调用 logo 的事件)pdf.ImageOptions("logo", x, x+20, 0, 0, false, opt, 0, "")}if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行效果:

3.11.1 注册图像的优势

pdf.RegisterImageOptionsReader() 最大的优势在于:一次注册,到处使用
调用 pdf.RegisterImageOptionsReader() 方法进行注册时,不会立即使用这张图片(不会立即渲染这张图片),需要在 pdf.Image() 系列方法中使用这个注册名称时,才会将图片渲染到 pdf 中。
如果某张图片会重复多次使用,那就把这张图片通过 pdf.RegisterImageOptionsReader() 方法,注册成图像信息。之后就可以反复使用这个图像信息的名称来渲染这张图片,再也不用每用一次都去重新读取、重新设置了,提高效率。

3.12 制作相框样式的 pdf

gofpdf.New() 方法中的 orientationStr 这个字符串参数,设置成字符串值 L。即可让 pdf 变成相框样式。
代码来源:fpdf_test.go#L892

func loremList() []string {return []string{"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod " +"tempor incididunt ut labore et dolore magna aliqua.","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +"aliquip ex ea commodo consequat.","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " +"dolore eu fugiat nulla pariatur.","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +"officia deserunt mollit anim id est laborum.",}
}func lorem() string {return strings.Join(loremList(), " ")
}func main() {var (y0       float64crrntCol int)const (pageWd = 297.0 //A4 210.0 x 297.0margin = 10.0gutter = 4colNum = 3colWd  = (pageWd - 2*margin - (colNum-1)*gutter) / colNum)//把 orientationStr 这个字符串参数,设置成 "L"。即可让 pdf 变成相框样式pdf := gofpdf.New("L", "mm", "A4", "")loremStr := lorem()setCol := func(col int) {crrntCol = colx := margin + float64(col)*(colWd+gutter)pdf.SetLeftMargin(x)pdf.SetX(x)}pdf.SetHeaderFunc(func() {titleStr := "gofpdf"pdf.SetFont("Helvetica", "B", 48)wd := pdf.GetStringWidth(titleStr) + 6pdf.SetX((pageWd - wd) / 2)pdf.SetTextColor(128, 128, 160)pdf.Write(12, titleStr[:2])pdf.SetTextColor(128, 128, 128)pdf.Write(12, titleStr[2:])pdf.Ln(20)y0 = pdf.GetY()})pdf.SetAcceptPageBreakFunc(func() bool {if crrntCol < colNum-1 {setCol(crrntCol + 1)pdf.SetY(y0)return false //开启新的一列,而不是新的一页}setCol(0)return true})pdf.AddPage()pdf.SetFont("Times", "", 12)for j := 0; j < 20; j++ {if j == 1 {pdf.Image("./images/fpdf.png", -1, 0, colWd, 0, true, "", 0, "")} else if j == 5 {pdf.Image("./images/golang-gopher.png", -1, 0, colWd, 0, true, "", 0, "")}pdf.MultiCell(colWd, 5, loremStr, "", "", false)pdf.Ln(-1)}if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行结果:


3.13 gocov 工具报告

涉及路径时,依然注意:要参照着 main.go 文件的所在目录来写 font 路径。
代码来源:fpdf_test.go#L952

func main() {var err errorerr = gofpdf.MakeFont("./font/CalligrapherRegular.pfb", "./font/cp1252.map", "font", nil, true)if err != nil {panic(err)return}pdf := gofpdf.New("", "", "", "")pdf.SetFontLocation("font")pdf.SetTitle("世界", true)pdf.SetAuthor("世界", true)pdf.SetSubject("世界", true)pdf.SetCreator("世界", true)pdf.SetKeywords("世界", true)pdf.AddFont("Calligrapher", "", "CalligrapherRegular.json")pdf.AddPage()pdf.SetFont("Calligrapher", "", 16)pdf.Writef(5, "\x95 %s \x95", pdf)if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行结果:

3.14 透明度的设置

代码来源:fpdf_test.go#L1060

func main() {const (gapX  = 10.0gapY  = 9.0rectW = 40.0rectH = 58.0pageW = 210)//设置透明度的方法:pdf.SetAlpha()。其中 blendModeStr 参数的值,只能是这些modeList := []string{"Normal", "Multiply", "Screen", "Overlay","Darken", "Lighten", "ColorDodge", "ColorBurn", "HardLight", "SoftLight","Difference", "Exclusion", "Hue", "Saturation", "Color", "Luminosity",}pdf := gofpdf.New("", "", "", "")pdf.SetLineWidth(2)pdf.SetAutoPageBreak(false, 0)pdf.AddPage()pdf.SetFont("Helvetica", "", 18)pdf.SetXY(0, gapY)pdf.SetTextColor(0, 0, 0)pdf.CellFormat(float64(pageW), gapY, "Alpha Blending Modes", "", 0, "C", false, 0, "")j := 0y := 3 * gapYfor col := 0; col < 4; col++ {x := gapXfor row := 0; row < 4; row++ {//画一个黑色的外框框pdf.Rect(x, y, rectW, rectH, "D")//框框的底部,画一个黑色的长方形。并在长方形中,写上白色的文字内容pdf.SetFont("Helvetica", "B", 12)pdf.SetFillColor(0, 0, 0)pdf.SetTextColor(250, 250, 230)pdf.SetXY(x, y+rectH-4)pdf.CellFormat(rectW, 5, modeList[j], "", 0, "C", true, 0, "")//外框框中,写上一个大写的斜体 Apdf.SetFont("Helvetica", "I", 150)pdf.SetTextColor(80, 80, 120)pdf.SetXY(x, y+2)pdf.CellFormat(rectW, rectH, "A", "", 0, "C", false, 0, "")//设置的 golang-gopher.png 透明度。注意:融合模式 blendModeStr 的值需要一个字符串pdf.SetAlpha(0.5, modeList[j])pdf.Image("./images/golang-gopher.png", x-gapX, y, rectW+2*gapX, 0, false, "", 0, "")//一个设置完了,把透明度重置为正常模式,方便进行下一个案例演示pdf.SetAlpha(1.0, "Normal")x += rectW + gapXj++}y += rectH + gapY}if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行结果:

3.15 生成书签

代码来源:fpdf_test.go#L1248

func main() {pdf := gofpdf.New("P", "mm", "A4", "")//添加 "仿宋" 字体pdf.AddUTF8Font("fangsong", "", "simfang.ttf")pdf.AddPage()//使用字体pdf.SetFont("fangsong", "", 15)pdf.Bookmark("阿毛生信系统简介", 0, 0) //顶级书签,并且不显示该书签在 pdf 中pdf.Bookmark("发展历程", 1, -1) //二级书签,名称叫“发展历程”,显示在 pdf 中pdf.Cell(0, 6, "发展历程") //点击“发展历程”这个书签,即会跳转到 pdf 中“发展历程”的所在位置pdf.Ln(100) //空出几行,为了演示跳转效果pdf.Bookmark("荣誉资质", 1, -1) //二级书签,名称叫“荣誉资质”,显示在 pdf 中pdf.Cell(0, 6, "荣誉资质") //点击“荣誉资质”这个书签,即会跳转到 pdf 中“荣誉资质”的所在位置pdf.AddPage()pdf.Bookmark("我们的产品", 0, 0)pdf.Bookmark("核心产品简介", 1, -1)pdf.Cell(0, 6, "核心产品简介")if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err)}
}

运行结果:
点击书签,即会跳转到各自书签的所在位置。

3.15.1 pdf.Bookmark() 方法

该方法的完整参数列表:func (f *Fpdf) Bookmark(txtStr string, level int, y float64)
官方文档对于这个方法的描述:

Bookmark sets a bookmark that will be displayed in a sidebar outline. txtStr is the title of the bookmark. level specifies the level of the bookmark in the outline; 0 is the top level, 1 is just below, and so on.
y specifies the vertical position of the bookmark destination in the current page; -1 indicates the current position.

Google 翻译+自己的理解:
设置一个书签,该书签将显示在侧边栏轮廓中。txtStr 是书签的标题。level 指定大纲中书签的级别:0 是最高级别,1 处于 0 之下,依此类推。
y 指定书签目标在当前页面中的垂直位置。-1 表示当前位置,0 表示不显示到 pdf 中。

3.16 pdf 添加其他的 pdf

3.16.1 需求

读取一个已存在的 pdf 中的内容,添加到新的 pdf。
感谢网友 @yellowriver007 提出了这个需求,使我有机会接触到了这个知识点。

3.16.2 准备工作

1.一个有内容的 pdf 文件。
2.下载好以下 2 个第三方 Go 依赖库:

"github.com/jung-kurt/gofpdf"
"github.com/jung-kurt/gofpdf/contrib/gofpdi"

3.16.3 键入代码

// 需求:读取一个本地 pdf 中的内容,写入到一个新的 pdf 中
/*
实现该需求的第三方库的地址:
https://github.com/jung-kurt/gofpdf/tree/master/contrib/gofpdi作者给出的教程代码:
https://github.com/jung-kurt/gofpdf/blob/master/contrib/gofpdi/gofpdi_test.go网友对此库使用的交流,建议看看,会有收获:
https://github.com/jung-kurt/gofpdf/issues/258
*/package mainimport ("bytes""fmt""github.com/jung-kurt/gofpdf""github.com/jung-kurt/gofpdf/contrib/gofpdi""io"
)// 作者给出的教程:新生成一个 pdf 文件,返回这个 pdf 的 io,作为源文件。
// 此例是读取一个本地的 pdf 中的内容,然后写入到新的 pdf 文件里。因此,不使用作者的这个函数。
// https://github.com/jung-kurt/gofpdf/blob/master/contrib/gofpdi/gofpdi_test.go#L66
func getTemplatePdf() (io.ReadSeeker, error) {tpdf := gofpdf.New("P", "pt", "A4", "")tpdf.AddPage()tpdf.SetFont("Arial", "", 12)tpdf.Text(20, 20, "Example Page 1")tpdf.AddPage()tpdf.Text(20, 20, "Example Page 2")tbuf := bytes.Buffer{}err := tpdf.Output(&tbuf)return bytes.NewReader(tbuf.Bytes()), err
}func main() {// create new pdfpdf := gofpdf.New("P", "pt", "A4", "")// for testing purposes, get an arbitrary template pdf as stream//rs, _ := getTemplatePdf()// create a new Importer instanceimp := gofpdi.NewImporter()// import first page and determine page sizes//tpl := imp.ImportPageFromStream(pdf, &rs, 1, "/MediaBox")// 这里是对一个本地的 pdf 的读取// `./1.pdf` 这个路径,换成自己 pdf 文件的所在路径tpl := imp.ImportPage(pdf, "./1.pdf", 1, "/MediaBox")pageSizes := imp.GetPageSizes()nrPages := len(imp.GetPageSizes())// add all pages from template pdffor i := 1; i <= nrPages; i++ {pdf.AddPage()if i > 1 {//tpl = imp.ImportPageFromStream(pdf, &rs, i, "/MediaBox")// `./1.pdf` 这个路径,换成自己 pdf 文件的所在路径tpl = imp.ImportPage(pdf, "./1.pdf", i, "/MediaBox")}imp.UseImportedTemplate(pdf, tpl, 0, 0, pageSizes[i]["/MediaBox"]["w"], pageSizes[i]["/MediaBox"]["h"])}if err := pdf.OutputFileAndClose("./test.pdf"); err != nil {panic(err)}fmt.Println("done")
}

编译、运行,打开这个 test.pdf 文件,可以看到最终效果如下:

找不到其他有内容的 pdf 文件,拿了单位的 OA 文件作为源文件,部分敏感内容已涂抹,请见谅。已确认源 pdf 文件中的所有内容,都已写入了这个新的 test.pdf 文件。

3.16.4 目录结构

3.17 图片旋转

3.17.1 首先看下效果

直接上代码:

package mainimport ("github.com/jung-kurt/gofpdf"
)func main() {//设置页面参数pdf := gofpdf.New("P", "mm", "A4", "")//添加一页pdf.AddPage()pdf.TransformBegin()            // begin transformpdf.TransformRotate(45, 70, -5) // 逆时针旋转 45 度//将图片放入到 pdf 文档中//ImageOptions(src, x, y, width, height, flow, options, link, linkStr)pdf.ImageOptions("1.jpg",0, 0,0, 0,false,gofpdf.ImageOptions{ImageType: "jpg", ReadDpi: false},0,"",)pdf.TransformEnd() // end transform// 新添加一页pdf.AddPage()pdf.TransformBegin()pdf.TransformRotate(-45, 0, 60) // 顺时针旋转 45 度pdf.ImageOptions("1.jpg",0, 0,0, 0,false,gofpdf.ImageOptions{ImageType: "jpg", ReadDpi: false},0,"",)pdf.TransformEnd()if err := pdf.OutputFileAndClose("1.pdf"); err != nil {panic(err.Error())}
}

运行代码,打开生成的 pdf,可以看到效果如下:

3.17.2 pdfg.TransformRotate() 方法的描述

作者对于这个方法的描述如下:

TransformRotate rotates the following text, drawings and images around the center point (x, y). angle is specified in degrees and measured counter-clockwise from the 3 o'clock position.
The TransformBegin() example demonstrates this method.

Google 翻译大意:“变换旋转”使以下文本,图形和图像围绕中心点(x,y)旋转。 角度以度为单位指定,并从3点钟位置逆时针测量。
没看懂这个描述,因此图片旋转后,我一直调不好图片的位置,恳请各位看官指点。

一些值得注意的点,以及遇到的坑

1 Windows下最好别用记事本

在学习 3.3.1 案例的时候,使用了记事本去写一些中文+英文。好了,一运行,PDF 的第一个字符一直都是一个空心的方块。想了半天,才记起来,Windows 的记事本会出现 “boom 头”,这个讨厌的东西。“boom 头” 是 3 个无法显示的空字符,这个是 Windows 特有的问题。Unix 以及 Linux 系统,不会有这个问题。后来在云服务器上用 vim 写了内容,再下载到本地。再次运行代码,就没有任何问题了。

参考文献

1.极简入门
2.link1
3.link2
4.gofpdf 官方文档

gofpdf 学习笔记相关推荐

  1. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  2. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  3. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  4. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  5. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

  6. 知识图谱学习笔记(1)

    知识图谱学习笔记第一部分,包含RDF介绍,以及Jena RDF API使用 知识图谱的基石:RDF RDF(Resource Description Framework),即资源描述框架,其本质是一个 ...

  7. 计算机基础知识第十讲,计算机文化基础(第十讲)学习笔记

    计算机文化基础(第十讲)学习笔记 采样和量化PictureElement Pixel(像素)(链接: 采样的实质就是要用多少点(这个点我们叫像素)来描述一张图像,比如,一幅420x570的图像,就表示 ...

  8. Go 学习推荐 —(Go by example 中文版、Go 构建 Web 应用、Go 学习笔记、Golang常见错误、Go 语言四十二章经、Go 语言高级编程)

    Go by example 中文版 Go 构建 Web 应用 Go 学习笔记:无痕 Go 标准库中文文档 Golang开发新手常犯的50个错误 50 Shades of Go: Traps, Gotc ...

  9. MongoDB学习笔记(入门)

    MongoDB学习笔记(入门) 一.文档的注意事项: 1.  键值对是有序的,如:{ "name" : "stephen", "genda" ...

  10. NuGet学习笔记(3) 搭建属于自己的NuGet服务器

    文章导读 创建NuGetServer Web站点 发布站点到IIS 添加本地站点到包包数据源 在上一篇NuGet学习笔记(2) 使用图形化界面打包自己的类库 中讲解了如何打包自己的类库,接下来进行最重 ...

最新文章

  1. Oracle数据库管理----性能优化
  2. 查看MongoDB索引的使用,管理索引
  3. 怎么提高python能力_怎样提高python分析数据能力?
  4. 针对SAP OData 模型Addresssable属性的不同处理
  5. linux驱动调试--oops信息
  6. poj 1060 Modular multiplication of polynomials
  7. asp.net core 3.0 gRPC框架小试
  8. C++笔记-初步窥探全局函数在Debug、Release的地址
  9. 使用启动外部进程的方式解决there should be only one application object(使用qtwinmigrate中常出现)
  10. 计算机专业是理科吗,计算机类和普通理科有什么区别?
  11. vb 循环放音乐_为何洒水车一直无限循环播放《兰花草》这首歌呢?
  12. 从外行到外包,从手工测试到知名互联大厂测开 我经历了我这个年龄段不该经历的事情...
  13. android5.1禁用通知栏,android-阻止通知栏
  14. Unitils集成DBUnit的问题-解决方案
  15. linux系统修改时区的方法
  16. SQLServer中sp_Who、sp_Who2和sp_WhoIsActive介绍和查看监视运行
  17. 思科 计算机网络 期末考试答案
  18. 什么是法线贴图 今天教你如何制作法线贴图
  19. 什么是IMS(IP多媒体子系统)
  20. 操作系统:磁盘调度算法FCFS算法(c语言)

热门文章

  1. 将计算机图标调整成光盘形状,win7系统本地磁盘图标变成U盘形状的解决方法
  2. linux密码weak,linux中__weak关键字
  3. 欧洲杯:匈牙利vs葡萄牙时间:06-15 23:59 星期二
  4. python函数调用键盘热键_Python自定义快捷键,热键,HotKey
  5. Ubuntu 16.04 安装并创建快捷图标 XMind
  6. 一篇博客收能收录计算机网络?
  7. FD-GAN: Pose-guided Feature Distilling GAN for Robust Person Re-identification
  8. 微信自动回复的智能聊天机器人怎么做?
  9. 35 漂亮的单页网页设计
  10. 计算机专用计算器,功率计算器