天天看點

利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖

項目中會時不時遇到展示pdf檔案的需求,比如需要展示某些合同或者一些PPT報告之類的。我們在做《娛樂寶》、《票票專業版》項目期間都遇到了這樣的需求。針對如何展現pdf檔案的内容,一般無外乎以下幾種方案:

讓用戶端渲染pdf

H5頁面通過開源的JS庫對PDF檔案進行渲染

将pdf列印為圖檔,然後再利用H5頁面對檔案中的多張圖檔分步下載下傳并渲染。

需用通過Ajax方式加載PDF檔案,而正式項目中,我們一般需要把PDF檔案上傳到CDN。

使用Canvas對PDF中的圖檔進行渲染,PDF中圖檔比較多的話将會生成大量的canvas

渲染出的結果存在相容性問題,不同字型設定會導緻渲染結果差異很大。

前兩個問題通過一些技術手段還能繞過去,但後兩個幾乎無解了,尤其是用canvas渲染圖檔。canvas在移動端太耗性能了,Canvas太多的話會造成浏覽器渲染性能嚴重下降,iphone下甚至會導緻APP崩潰。采用Canvas渲染圖檔的證據。

利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖
利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖

不同字型設定導緻渲染結果不一緻的問題:

利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖
利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖

此外在測試過程中還發現:那個庫提供的Demo頁面在UC下打開且打開的PDF檔案比較大的情況下,多翻幾頁之後會出現頁面加載不出來的情況,是以這個庫在H5下的相容問題堪憂。 是以采用JS庫渲染PDF目前來看不太适合應用在H5項目中,在PC項目中還可以考慮下。

如果由程式完成将PDF轉換為長圖,必須要實作兩個功能:

将PDF的每一頁轉換為圖檔

将轉換後的多張圖檔合并為一張長圖

還好這兩個功能都有相應的軟體支援,而且這兩個軟體的指令行支援都非常好,而且都支援brew進行安裝。将PDF轉換為圖檔最著名的庫莫過于GhostScript,在項目中我們也選用了這個庫。将PDF的每一頁轉換為圖檔可以通過下面的指令來實作

将多張圖檔拼合為一張有多種軟體可以實作,比較有名的是ImageMagick和GraphicsMagick。ImageMagick資曆最老,文檔最全,支援的特性最多,但運作起來比較緩慢。GraphicsMagick脫胎于ImageMagick 5.x,支援的特性比較少,指令格式幾乎與ImageMagick通用,運作速度飛快,但文檔非常少,而且有些特性不支援(本文後面程式中所使用的切功能:shave在測試時沒有調試通過)。考慮到這個功能無論在本機還是服務端調用都不是很頻繁,是以我們使用了ImageMagick。下面的代碼可以實作将多張圖檔拼接為一張長圖,為了輸出的圖檔更加美觀,圖檔之間添加了一定的白色空白。

有一點需要說明下,安裝GhostScript後,ImageMagick内部可以直接調用GhostScript實作将PDF轉換為長圖,具體實作可以參考如下:

這段代碼省去了第一步利用GhostScript将PDF轉換為多張圖檔的步驟,但效果不是很理想,無論怎麼設定分辨率(density)和JPG品質(quality),轉換出來的圖檔都有點糊,是以實際項目中我們使用了分開處理的方案。

因為操作比較多,我們寫了個bash腳本對這些邏輯做封裝,使用方式為:<code>bash convert.sh demo.pdf</code>,腳本完整代碼如下:

轉換後圖檔線上上的實際效果

利用指令行将pdf轉換為長圖利用指令行将pdf轉換為長圖

答案是可以,這一方案依賴的兩個軟體: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轉換為長圖的代碼:

<a href="http://www.runoob.com/linux/linux-shell-variable.html">http://www.runoob.com/linux/linux-shell-variable.html</a>

<a href="https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash/965072">https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash/965072</a>

<a href="https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash">https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash</a>

<a href="https://stackoverflow.com/questions/3362920/get-just-the-filename-from-a-path-in-a-bash-script">https://stackoverflow.com/questions/3362920/get-just-the-filename-from-a-path-in-a-bash-script</a>

<a href="https://stackoverflow.com/questions/653380/converting-a-pdf-to-png">https://stackoverflow.com/questions/653380/converting-a-pdf-to-png</a>

<a href="http://www.imagemagick.org/discourse-server/viewtopic.php?t=15523">http://www.imagemagick.org/discourse-server/viewtopic.php?t=15523</a>

<a href="http://www.imagemagick.org/Usage/crop/#border">http://www.imagemagick.org/Usage/crop/#border</a>

<a href="http://www.imagemagick.org/Usage/crop/#frame">http://www.imagemagick.org/Usage/crop/#frame</a>