1. 程式人生 > 程式設計 >Go處理PDF的實現程式碼

Go處理PDF的實現程式碼

工作中經常會遇到一些pdf檔案處理的問題,一千種pdf有一千種處理方式,每次都是絞盡腦汁和這些pdf戰鬥到底。

本人又是一個gopher,所以這篇文章會以一個goper的視角,列舉一下我所經歷過的每一種pdf處理場景,比如:

pdf渲染
pdf校驗
pdf加水印
pdf獲取頁數
pdf合併
pdf拆分
修復受損pdf
pdf轉png
識別pdf中的字型
pdf解密
...

本文大多是場景問題的羅列,可以根據標題摘取自己有興趣的部分檢視

很多pdf的問題我也不是特別專業,如果問題或者疑問歡迎與我交流

一、HTML頁面渲染PDF

根據html頁面渲染pdf,我使用過以下兩種方案:

  • wkhtmltopdf
  • chromedp

1. 使用wkhtmltopdf渲染pdf

wkhtmltopdf是一個命令列工具,用於將HTML頁面渲染為PDF,基於Qt WebKit渲染引擎實現

使用方式比較簡單:

## 將一個靜態html頁面列印成pdf
$ wkhtmltopdf input.html output.pdf

## 將一個網頁列印成pdf
$ wkhtmltopdf https://www.google.com output.pdf

wkhtmltopdf的引數很豐富,比如:

支援傳送 http post請求,適合將自定義開發的網頁渲染成pdf檔案:

$ wkhtmltopdf --help
...
--post <name> <value>      Add an additional post field (repeatable)
...

支援javascript指令碼,在渲染pdf前對html進行修改:

$ wkhtmltopdf --run-script "javascript:(function(){document.getElementsByClassName('dom_class_name')[0].style.display = 'none'}())" page input.html output.pdf

更多詳細引數可看官網文件

如果你使用Go語言,還有一個第三方包,是對wkhtmltopdf的使用封裝:go-wkhtmltopdf

2. 使用chromedp渲染pdf

chromedp是一種在Go語言中以更快,更簡單的方式來驅動支援Chrome DevTools協議的瀏覽器的軟體包,而無需外部依賴((例如Selenium或PhantomJS).

使用方式:

package main

import (
  "context"
  "io/ioutil"

  "github.com/chromedp/cdproto/page"
  "github.com/chromedp/chromedp"
  "errors"
)

func main(){
  err := ChromedpPrintPdf("https://www.google.com","/path/to/file.pdf")
  if err != nil {
    fmt.Println(err)
    return
  }
}

func ChromedpPrintPdf(url string,to string) error {
  ctx,cancel := chromedp.NewContext(context.Background())
  defer cancel()

  var buf []byte
  err := chromedp.Run(ctx,chromedp.Tasks{
    chromedp.Navigate(url),chromedp.WaitReady("body"),chromedp.ActionFunc(func(ctx context.Context) error {
      var err error
      buf,_,err = page.PrintToPDF().
        Do(ctx)
      return err
    }),})
  if err != nil {
    return fmt.Errorf("chromedp Run failed,err:%+v",err)
  }

  if err := ioutil.WriteFile(to,buf,0644); err != nil {
    return fmt.Errorf("write to file failed,err)
  }

  return nil
}

二、PDF加水印

我瞭解到的支援pdf加水印的工具有:

  • unidoc/unipdf
  • pdfcpu

1.unidoc/unipdf

unidoc平臺開發的unipdf是一款用Go語言編寫的PDF庫,提供API和CLI使用模式,支援以下功能:

$ unipdf -h
...
Available Commands:
 decrypt   Decrypt PDF files
 encrypt   Encrypt PDF files
 explode   Explodes the input file into separate single page PDF files
 extract   Extract PDF resources
 form    PDF form operations
 grayscale  Convert PDF to grayscale
 help    Help about any command
 info    Output PDF information
 merge    Merge PDF files
 optimize  Optimize PDF files
 passwd   Change PDF passwords
 rotate   Rotate PDF file pages
 search   Search text in PDF files
 split    Split PDF files
 version   Output version information and exit
 watermark  Add watermark to PDF files
...

CLI模式新增水印

$ unipdf watermark in.pdf watermark.png -o out.pdf

Watermark successfully applied to in.pdf
Output file saved to out.pdf

使用API新增水印,可以直接參考unipdf github example

注意:unidoc的產品需要付費購買license使用

2.pdfcpu

pdfcpu 是一個用Go語言編寫的PDF處理庫,提供API和CLI模式使用

支援以下功能:

$ pdfcpu help
...
The commands are:

  attachments list,add,remove,extract embedded file attachments
  changeopw  change owner password
  changeupw  change user password
  decrypt   remove password protection
  encrypt   set password protection
  extract   extract images,fonts,content,pages,metadata
  fonts    install,list supported fonts
  grid    rearrange pages or images for enhanced browsing experience
  import   import/convert images to PDF
  info    print file info
  merge    concatenate 2 or more PDFs
  nup     rearrange pages or images for reduced number of pages
  optimize  optimize PDF by getting rid of redundant page resources
  pages    insert,remove selected pages
  paper    print list of supported paper sizes
  permissions list,set user access permissions
  rotate   rotate pages
  split    split multi-page PDF into several PDFs according to split span
  stamp    add,update text,image or PDF stamps for selected pages
  trim    create trimmed version of selected pages
  validate  validate PDF against PDF 32000-1:2008 (PDF 1.7)
  version   print version
  watermark  add,image or PDF watermarks for selected pages
...

使用CLI工具以圖片形式新增水印:

$ pdfcpu watermark add -mode image 'voucher_watermark.png' 's:1 abs,rot:0' in.pdf out.pdf

呼叫api新增水印

package main

import (
  "github.com/pdfcpu/pdfcpu/pkg/api"
  "github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
)

func main() {
  onTop := false
  wm,_ := pdfcpu.ParseImageWatermarkDetails("watermark.png","s:1 abs,rot:0",onTop)
  api.AddWatermarksFile("in.pdf","out.pdf",nil,wm,nil)
}

三、PDF合併

  • cpdf
  • unipdfc
  • pdfcpu

1.使用cpdf合併pdf

cpdf是一個開源免費的PDF命令列工具庫,有豐富的功能,比如:

  • Merge PDF files together,or split them apart
  • Encrypt and decrypt
  • Scale,crop and rotate pages
  • Read and set document info and metadata
  • Copy,add or remove bookmarks
  • Stamp logos,text,dates,page numbers
  • Add or remove attachments
  • Losslessly compress PDF files

合併pdf:

$ cpdf -merge input1.pdf input2.pdf -o output.pdf

2.使用unipdf合併pdf

$ unipdf merge output.pdf input1.pdf input2.pdf

使用API合併pdf,參考unpdf github example

3.使用pdfcpu合併pdf

$ pdfcpu merge output.pdf input1.pdf input2.pdf

注意: pdfcpu只支援版本低於PDF V1.7的pdf檔案

四、拆分PDF

  • cpdf
  • unipdf
  • pdfcpu

1.使用cpdf拆分pdf

## 逐頁拆分成單個pdf
$ cpdf -split in.pdf 1 even -chunk 1 -o ./out%%%.pdf

2. 使用unipdf拆分pdf

## 將第一頁拆分出來
$ unipdf split input.pdf out.pdf 1-1

使用api拆分pdf,參考unipdf github examples

3.使用pdfcpu拆分pdf

$ pdfcpu split in.pdf .

五、PDF轉圖片

  • mupdf
  • xpdf

1. 使用mupdf操作pdf轉圖片

MuPDF is a lightweight PDF,XPS,and E-book viewer.
MuPDF consists of a software library,command line tools,and viewers for various platforms.

下載mupdf後得到一些工具,比如:

mupdf
pdfdraw
pdfinfo
pdfclean
pdfextract
pdfshow
xpsdraw

其中pdfdraw可用來轉換圖片

$ pdfdraw -o out%d.png in.pdf

注意: mupdf不支援mac OS

2. 使用xpdf操作pdf轉圖片

xpdf是一個免費的PDF工具包,包括文字解析,圖片轉換,html轉換等

下載該軟體包後,可以得到一系列的工具:

pdfdetach
pdffonts
pdfimages
pdfinfo
pdftohtml
pdftopng
pdftoppm
pdftops
pdftotext

從名稱上看,大致能看出來每一個工具的用處

## 使用pdftopng將pdf轉換成png
$ pdftopng in.pdf out-prefix

六、PDF解密

經常會遇到一種場景,讀取pdf檔案的時候發現會報錯:檔案被加密

但是在沒有密碼的情況下怎麼解決呢?

  • 使用qpdf解密

使用qpdf進行強制解密,有些情況是可以解密成功的,但是有些情況也不一定能解密成功

qpdf是一個支援命令列的pdf工具

$ qpdf --decrypt in.pdf out.pdf

使用pdfcpu解密

$ pdfcpu decrypt encrypted.pdf output.pdf

當有密碼的情況下,可以使用密碼解密:

使用unipdf解密pdf

$ unipdf decrypt -p pass -o output.pdf input.pdf

七、PDF識別

經常會遇到一些場景,比如識別一個檔案是不是pdf檔案,識別pdf中的文字,識別pdf中的圖片等

1.識別pdf中的文字

這裡使用xpdf將pdf中的文字解析出來,然後再使用一些字串操作或者正則表示式進行業務分析

使用xpdf/pdftotext解析pdf中的文字

$ pdftotext input.pdf output.txt

使用unipdf解析pdf中的文字

$ unipdf extract text input.pdf

使用API解析pdf文字,參考unipdf github examples

使用座標資訊解析pdf資料

上面都是先解析出pdf的文字,再根據業務進行處理

還有一種方式是按照座標位置解析pdf,這種方式更加靈活以及通用,利用的是pdflib/tet

## 輸入一組座標,即可按照座標解析pdf中的資料
$ tet --pageopt "includebox={{38 707.93 243.91 716.93}}" input.pdf

座標可以使用tet對pdf進行分析得到一個tetml檔案,裡面包含了座標資訊:

$ tet --tetml input.pdf

當然也可以用一些其他的方式獲取pdf中資料的座標資訊,比如nodejs等

注意: pdflib/tet是收費軟體,但是根據官方文件說明,tet提供基礎功能,處理不超過10頁或者小於1M的pdf檔案是不需要購買license的

pdflib/tet提供了命令列工具以及多種語言的sdk支援,比如C/C++/Java/.NET/Perl/PHP/Python/Ruby/Swift 但目前還不支援Go語言,所以對於gopher而言目前只有兩種選擇:CLI OR CGO

八、修復受損PDF檔案

有一些pdf檔案在電腦上開啟時,顯示正常,但是用程式碼檢測卻是不正常的,比如在Go中嘗試用一個第三方庫去解析一個(受損的)pdf:

import (
  "fmt"
  "github.com/rsc.io/pdf"
)

func main() {
  filePath := "path/to/your/broken.pdf"
  _,err := pdf.Open(filePath)
  if err != nil {
    fmt.Println("open pdf failed,err:",err.Error())
    return
  }
}

執行後會得到這樣一個結果:

open pdf failed,err: malformed PDF: cross-reference table not found: {5 0 obj}<</Contents 6 0 R /Group <</CS /DeviceRGB /S /Transparency /Type /Group>> /MediaBox [0 0 595.27600098 841.89001465] /Parent 3 0 R /Type /Page>>

電腦開啟正常,程式卻讀取錯誤!

這時候如果嘗試在電腦上開啟pdf,然後另存為一個新的pdf檔案,再用程式碼去檢測,會發現竟然修復了!

太好了,問題解決!

等等,如果我有1000張pdf檔案,難道要逐個開啟並另存為?這怎麼能忍? 所以如果有一種批量修復的功能就好了

在網上找了很久,大概得到三種解決方案:

  • 利用 Acrobat SDK,呼叫SDK中的另存為功能,可以實現電腦開啟另存為的效果
  • 利用ghostscript進行pdf修復
  • 利用mupdf進行pdf修復

這裡我只驗證了第三種方式是可行的,這裡我使用mupdf-0.9-linux-amd64這個版本進行驗證

下載軟體包後,得到其中一個可執行檔案:pdfclean

$ pdfclean broken.pdf repaired.pdf

+ pdf/pdf_xref.c:160: pdf_read_trailer(): cannot recognize xref format: '%'
| pdf/pdf_xref.c:481: pdf_load_xref(): cannot read trailer
\ pdf/pdf_xref.c:537: pdf_open_xref_with_stream(): trying to repair

從輸出結果來看,mupdf嘗試了修復處理

得到新的pdf檔案之後,再用前面的Go程式碼嘗試開啟,就正常了

剩下的就是寫一個bash指令碼,批量修復,目標達成!

九、識別一個PDF檔案的字型資訊

有時候要使多個pdf文字字型保持一致,免不得要去分析pdf中都使用了哪些字型,這時候可以使用xpdf/pdffonts進行字型分析

$ pdffonts input.pdf
name                 type       encoding     emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
NimbusSanL-Regu           CID TrueType   Identity-H    yes no yes   10 0
NimbusSanL-Bold           CID TrueType   Identity-H    yes no yes   20 0

其他Libiray介紹:

PDF-Writer
這是一個C++的開源庫,支援建立pdf,合併pdf,圖片水印文字操作等

對於gopher來講,要使用這個庫,需要封裝一層CGO程式碼才可以

rsc/pdf
這是一個Go語言實現的pdf庫,可以用於讀取pdf資訊,比如讀取pdf內容/頁數/字型等... 具體可以參考文件

介紹了這麼多第三方庫,簡直就是五花八門,各顯神通。有些功能在大多數庫中都是有重複的,具體使用中會遇到什麼問題,還是要看實際情況如何。

希望這些總結能夠對讀者有所幫助

參考:

wkhtmltopdf
xpdf
cpdf
qpdf
unidoc
pdflib/tet
pdfwriter
mupdf
pdfcpu

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。