利用命令列將pdf轉換為長圖
利用命令列將pdf轉換為長圖
業務場景
專案中會時不時遇到展示pdf檔案的需求,比如需要展示某些合同或者一些PPT報告之類的。我們在做《娛樂寶》、《票票專業版》專案期間都遇到了這樣的需求。針對如何展現pdf檔案的內容,一般無外乎以下幾種方案:
- 讓客戶端渲染pdf
- H5頁面通過開源的JS庫對PDF檔案進行渲染
- 將pdf列印為圖片,然後再利用H5頁面對檔案中的多張圖片分步下載並渲染。
而這幾種方案在實際操作過程中又分別有各自的問題,我們首先討論第一種方案。讓客戶端渲染PDF有兩種方式:1. 藉助系統已有的功能,比如webbiew。2. 利用開源的pdf渲染庫。iOS中webview自帶了pdf渲染功能,同時支援縮放等操作,體驗相當好,但安卓不支援,需要自己開發實現。而第三方PDF渲染庫普遍比較大,一般都要好幾兆,為了這樣一個非核心功能引入這麼大一個庫,客戶端同學是堅決不會答應的。第二種方案初看起來不錯,至少省去了客戶端相容的成本。調研了下JS渲染PDF方面的實現,比較著名的是Mozzila的pdf.js。但這個庫也有一些問題:
- 這個庫的原始檔體積不小,原始檔282K,gzip壓縮後110K。
- 需用通過Ajax方式載入PDF檔案,而正式專案中,我們一般需要把PDF檔案上傳到CDN。
- 使用Canvas對PDF中的圖片進行渲染,PDF中圖片比較多的話將會生成大量的canvas
- 渲染出的結果存在相容性問題,不同字型設定會導致渲染結果差異很大。
前兩個問題通過一些技術手段還能繞過去,但後兩個幾乎無解了,尤其是用canvas渲染圖片。canvas在移動端太耗效能了,Canvas太多的話會造成瀏覽器渲染效能嚴重下降,iphone下甚至會導致APP崩潰。採用Canvas渲染圖片的證據。
不同字型設定導致渲染結果不一致的問題:
此外在測試過程中還發現:那個庫提供的Demo頁面在UC下開啟且開啟的PDF檔案比較大的情況下,多翻幾頁之後會出現頁面載入不出來的情況,因此這個庫在H5下的相容問題堪憂。 因此採用JS庫渲染PDF目前來看不太適合應用在H5專案中,在PC專案中還可以考慮下。
經過排除後目前只剩下將PDF轉換為圖片然後用H5來渲染這一方案了,至於此方案的具體實現,可以參照之前釋出的一篇文章《一個簡單H5活動頁面模板的設計》。這個方案比較簡單可靠,但面臨一個很煩人的問題:需要將PDF的每一頁轉換為圖片然後拼接為長圖。如果這個過程需要人工來完成將是非常繁瑣的,而如果檔案比較大的話那簡直是噩夢了,因此這個過程是必須由程式自動來完成的。
利用命令將PDF自動轉換為長圖
如果由程式完成將PDF轉換為長圖,必須要實現兩個功能:
- 將PDF的每一頁轉換為圖片
- 將轉換後的多張圖片合併為一張長圖
還好這兩個功能都有相應的軟體支援,而且這兩個軟體的命令列支援都非常好,而且都支援brew進行安裝。將PDF轉換為圖片最著名的庫莫過於GhostScript,在專案中我們也選用了這個庫。將PDF的每一頁轉換為圖片可以通過下面的命令來實現
gs -sDEVICE=pngalpha # 輸出格式為png
-o "./tmp-pdf-page/$filename-%d.png" # 設定每一頁對應圖片的名稱
-r144 "$pdfname"; # 設定每英寸內的畫素數
將多張圖片拼合為一張有多種軟體可以實現,比較有名的是ImageMagick和GraphicsMagick。ImageMagick資歷最老,文件最全,支援的特性最多,但執行起來比較緩慢。GraphicsMagick脫胎於ImageMagick 5.x,支援的特性比較少,命令格式幾乎與ImageMagick通用,執行速度飛快,但文件非常少,而且有些特性不支援(本文後面程式中所使用的切功能:shave在測試時沒有除錯通過)。考慮到這個功能無論在本機還是服務端呼叫都不是很頻繁,因此我們使用了ImageMagick。下面的程式碼可以實現將多張圖片拼接為一張長圖,為了輸出的圖片更加美觀,圖片之間新增了一定的白色空白。
convert ./tmp-pdf-page/$filename-*.png
-background white
-bordercolor white # 設定圖片邊框顏色
-border 0x50 # 圖片上下新增50畫素邊框 ,因此圖片之間有100px的邊框
-append # 圖片直接垂直拼接,如果水平拼接可用+append
-shave 0x50 # 刪除合併後圖片的上下邊框,GraphicsMagick不支援此操作
-resize 1080 # 將拼接後的圖片寬度調整為1080
-quality 85 # 設定輸出的JPG圖片質量
-sharpen 0x1.0 # 拼接後的圖片字型有點發虛,在垂直方向做銳化處理
$filename-dest.jpg
有一點需要說明下,安裝GhostScript後,ImageMagick內部可以直接呼叫GhostScript實現將PDF轉換為長圖,具體實現可以參考如下:
convert demo.pdf
-resize 620 # 設定每張圖片尺寸
-alpha remove
-density 620 # 設定解析度,按文件應該越高越好
-mattecolor `#cccccc` # 設定間隔顏色,作用與上面程式碼中的border相同
-frame 10x5 # 設定圖片間隔寬度
-append
-quality 85
-frame 0x5
-sharpen 0x1.0
demo.jpg
這段程式碼省去了第一步利用GhostScript將PDF轉換為多張圖片的步驟,但效果不是很理想,無論怎麼設定解析度(density)和JPG質量(quality),轉換出來的圖片都有點糊,因此實際專案中我們使用了分開處理的方案。
因為操作比較多,我們寫了個bash指令碼對這些邏輯做封裝,使用方式為:bash convert.sh demo.pdf
,指令碼完整程式碼如下:
#!/bin/bash
## 計算pdf檔名,參考資料:
# http://www.runoob.com/linux/linux-shell-variable.html
# https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash/965072
# https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash
# https://stackoverflow.com/questions/3362920/get-just-the-filename-from-a-path-in-a-bash-script
#
pdfname=$1
filename="${pdfname%%.*}"
## 建立臨時資料夾儲存每張pdf頁面對應的圖片
mkdir tmp-pdf-page
## 將pdf轉換為多張png
gs -sDEVICE=pngalpha -o "./tmp-pdf-page/$filename-%d.png" -r144 "$pdfname";
## 將多張圖片合併為一張,每張圖片直接新增50畫素間隔,最後
# 將圖片尺寸設定1080寬度後裁掉第一張和最後一張的邊框,並
# 進行銳化處理後輸出為jpg。
# 參考資料:
# http://www.imagemagick.org/Usage/crop/#border
# http://www.imagemagick.org/Usage/crop/#frame
convert ./tmp-pdf-page/$filename-*.png
-background white
-bordercolor white
-border 0x50
-append
-shave 0x50
-resize 1080
-quality 85
-sharpen 0x1.0
$filename-dest.jpg
## 刪除單張pdf檔案對應的圖片
rm -rf ./tmp-pdf-page
轉換後圖片線上上的實際效果
是否可以應用到服務端?
答案是可以,這一方案依賴的兩個軟體:ImageMagick和GhostScript在Linux和Mac下均有提供,所以可以無縫移植的服務端。最早做這個方案的研究是在一年多以前,當時在做《娛樂寶》專案,每個專案上線都要上傳合同,所以把生成圖片並上傳CDN的功能做到了小二後臺中。當時是直接利用ImageMagick將PDF轉換為長圖的功能,沒有使用先用GhostScript轉換為多圖然後再用ImageMagick拼接的方案。當時的效果不是很理想,文字總是比較糊。但當時一來沒有找到理想的解決方案,二來支付寶對於圖片的大小有要求,所以就將就著用了。後來專案中又遇到了這個需求,所以花了些時間整理和優化了下,所以有了本文提到的這個方案。
移植到服務端沒有問題,但有幾點需要注意下:
- 服務端環境一般都沒有安裝ImageMagick,需要自己手動安裝。而且Linux版本的ImageMagick處於安全考慮是不能直接完成pdf轉圖片的,需要對配置檔案進行一些配置。具體配置很簡單,基本看一眼就懂了。
- Linux環境下中文字型普遍比較少,好像只有宋體,所以轉換出來的效果沒有Mac下好看。如果這種需求的頻率比較低且對最終的轉化效果由一些要求,建議還是在Mac下進行轉換。
- 本文的bash指令碼方案會產生臨時檔案,不建議部署到服務端!
後記
目前這個方案還是不是特別理想,一個讓人很不爽的地方是:因為每個pdf頁面都需要生成一張圖片,所以程式執行期間需要建立多個臨時檔案。我一向對臨時檔案深惡痛絕,因為臨時檔案不僅會憑空增加磁碟訪問量,而且如果管理不好的話會造成垃圾檔案越堆越多,而如果不巧這個程式執行在服務端那就有可能把磁碟都佔滿了。在寫此文之前,我曾嘗試了多個方法把這個臨時檔案幹掉,但最終都不是很理想。
首先GhostScript提供將結果輸出到標準IO的功能,但ImageMagick的append功能無法支援從標準IO讀取多張圖片檔案,因此此方案行不通。GraphicsMagick也不支援從命令列讀取多張方案,但gm與GhostScript協同呼叫的效果比ImageMagick的效果要好,轉換後的效果與本文中用兩部實現的效果相當,但需要自己手動計算PDF頁數,而且因為不支援-shave引數,需要自己手動對最後轉換後的圖片進行必要的裁切。我們的使用場景主要是開發本機呼叫,開發時間所限,沒有對GraphicsMagick方案進行進一步調研。如果是部署到服務端,建議使用GraphicsMagick,不僅效率高而且不會產生臨時檔案,GraphicsMagick直接將PDF轉換為長圖的程式碼:
gm convert -density 1080
-mattecolor red
-frame 0x50
-append
-shave 0x50 # 裁剪功能在GM下沒有生效,不知是否是使用不當還是這種情況下不支援。
-resize 1080
-quality 85
test.pdf[1-4] # 這裡需要手動制定要轉換的頁碼範圍
test-tmp.jpg
參考資料
- http://www.runoob.com/linux/linux-shell-variable.html
- https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash/965072
- https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash
- https://stackoverflow.com/questions/3362920/get-just-the-filename-from-a-path-in-a-bash-script
- https://stackoverflow.com/questions/653380/converting-a-pdf-to-png
- http://www.imagemagick.org/discourse-server/viewtopic.php?t=15523
- http://www.imagemagick.org/Usage/crop/#border
- http://www.imagemagick.org/Usage/crop/#frame
相關文章
- 利用vbs指令碼將word文件轉換為pdf指令碼
- 命令列下將 word 轉 pdf命令列
- aspose word轉換pdf檔案後將pdf檔案轉換為圖片png
- PHP利用JSON將XML轉換為陣列PHPJSONXML陣列
- Java將地圖轉換為陣列[Snippet]Java地圖陣列
- Java 將PDF轉為PDF/AJava
- C# 將PDF文件轉換為Markdown文件C#
- JavaScript將陣列轉換為字串JavaScript陣列字串
- 怎麼將pdf轉換成jpg圖片格式
- PDF批次轉換器,批次轉word為pdf,批次轉ppt為pdf
- Python實現批次將ppt轉換為pdfPython
- Python實現批量將ppt轉換為pdfPython
- Java 將PDF轉為透明背景的圖片Java
- Python 將PDF轉為PDF/A、PDF/X,以及PDF/A轉回PDFPython
- jQuery將類陣列物件轉換為陣列jQuery陣列物件
- 利用 Adobe Reader 的API將 PDF 轉換成 BMPAPI
- Java 將PDF轉為線性PDFJava
- 如何將 PowerPoint 簡報轉換為 PDF 檔案?
- Java 將Markdown檔案轉換為Word和PDF文件Java
- 圖片怎麼轉換成PDF,圖片轉PDF教程
- 將“PDF轉換成PPT”與“PPT轉PDF”的方法
- 圖片格式轉換,JPG圖片轉換成PDF
- 將字串陣列轉換為浮點數陣列字串陣列
- js將偽陣列或者集合轉換為陣列JS陣列
- 利用Qt將網頁儲存為PDFQT網頁
- Java如何將字串轉換為字元陣列?Java字串字元陣列
- T-SQL——將字串轉換為多列SQL字串
- js如何將陣列元素轉換為字串JS陣列字串
- js將dom元素集合轉換為陣列JS陣列
- 逆向工程——利用PowerDesigner將表結構轉為物理資料模型並轉換為圖片模型
- 使用C#,VB和Java將PDF轉換為DOC / DOCXJava
- 蘋果手機如何將PDF檔案轉換為Word文件蘋果
- Java中將XML轉換為PDF的兩種辦法JavaXML
- cad圖紙怎麼轉換成pdf格式?Cad快速轉為pdf的方法
- C# 將PDF轉為線性化PDFC#
- js利用Number()函式將字串轉換為數字JS函式字串
- Java將彩色PDF轉為灰度Java
- C# 將PDF轉為ExcelC#Excel