1. 程式人生 > >flying saucer做導PDF踩過的坑~

flying saucer做導PDF踩過的坑~

     最近公司要求做一個匯出PDF報表的功能。由於時間比較緊張,而且匯出的內容暫時為一個報表而已,所以我採用了flying saucer+freemaker來做。

flying saucer是基於itext的,其最大的優勢,是對css2.1的支援,頁面渲染效果很好~特別是在做html轉PDF時。話不多說,你看!

1.jar引入

Gradle專案:

compile ('org.xhtmlrenderer:flying-saucer-pdf:9.0.8') 

Maven專案:

<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.0.8</version>
</dependency>

具體的PDFUtils程式碼下次補上~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(華麗分割線)~~~~~~~~~~~~~~~~~~~~~~~~~~~

***************************************坑點大全******************************************

1.中文支援問題

因為這個框架是外國人寫的,中文對於外國人太過複雜,特別是支援語言包的編寫上。所以,所有的flying-saucer包均不支援中文。

第一步:雖然不支援中文,但是外國人給我們一個字型類:BaseFont類。只要在這個類中,加入你想要支援的字型檔案(.afm、.pfm、.ttf、.otf、.ttc均支援哦,有興趣的同學可以去研究下這個BaseFont原始碼,歡迎評論區留言~),具體程式碼如下:

ITextRenderer render = new ITextRenderer();
render.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/*
引數說明:
fontPath字型的路徑
BaseFont.IDENTITY_H 字型水平書寫 IDENTITY_V 為垂直書寫
BaseFont.NOT_EMBEDDED 字型不需要嵌入
BaseFont Api詳細:https://www.coderanch.com/how-to/javadoc/itext-2.1.7/com/lowagi/text/pdf/BaseFont.html
*/

第二步:在你的html上加上字型!html上加上字型!html上加上字型!重要的事情說三遍!!!

<body style = "font-family: SimHei; font-size:small;"></body>

2.中文換行問題

org.xhtmlrenderer:flying-saucer-pdf這個jar包在9.0.1版本之前都是不支援中文換行的。(解決方案:需要對Jar包中的Breaker.class進行修改,很多國內大佬都有修改方案,詳細請看:https://blog.csdn.net/conan1210/article/details/50750849)

附上修改主要程式碼:

private static boolean isChinese(char c){
    Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);  
    if(ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || 
    ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS || 
    ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || 
    ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || 
    ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || 
    ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS){
        return true;
    }
    return false;
}
  
private static int getStrRight(String s, int left){
 if(left >= s.length()) return -1;
 char[] ch = s.toCharArray();
 for(int i = left; i < ch.length; i++){ 
    if(isChinese(ch[i]) || ' ' == ch[i]){
     return i==0?i+1:i; 
    } 
 } 
return -1;
}

2.1 這樣修改原始碼只是對中文做了一個簡單的換行處理,並不能自動識別標點比如逗號或者表單資料中,句段的合理換行。所以,為了能讓你的PDF好看點,強烈不推薦使用9.0.1及其之前的版本!!!

3.路徑問題(這個絕對是最最最最坑的,差點讓我放棄flying saucer)

3.1 首先,讓我們來研究下:不同系統的路徑問題。(主要是:windows和linux)

就拿字型檔案路徑舉例:比如我需要引入專案中 resources/fonts/simhei.ttf      

在windows系統上:

不管你用ClassLoaber.getResource()或System.getProperty("user.dir")或者getServletContext().getRealPath("/")。。。只要能獲取專案的絕對路徑都OK。輸出的路徑應為這樣的形式: /E:/MyWorkspace/專案名/xxx/classes/

String fontPath = Thread.currentThread().getContextClassLoader(). getResource(font).
getPath();
//font為:fonts/simhei.ttf
render.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
在linux系統上:

解決方案:建議把simhei.ttf字型檔案放在在linux系統中 : /user/share/font/simhei.ttf  

render.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//fontPath為: "/usr/share/simhei.ttf"

3.2 然後,我們再來研究下要匯出的html中有圖片,圖片的路徑

圖片的路徑也必須是絕對路徑才可以識別哦~程式碼如下:

render.getSharedContext().setBaseURL("file://"+Thread.currentThread().getContextClassLoader().getResource(imagePath).getPath());

//imagePath 為:圖片在html中使用的路徑
//這裡路徑eg : file:///E:...的路徑,在windows和linux中均可以識別。

3.3 最後,這裡還有個大坑,就是專案的所需資源打成外jar包時,路徑訪問的問題,是不支援new File()讀取檔案的!!!

我在使用getFontResolver().addFont()這個方法時,由於生產環境的專案是打成jar包的形式,居然無法識別路徑,無法指定到字型檔案或圖片。經過千辛萬苦,不斷讀原始碼,終於找到原因:https://blog.csdn.net/b_h_l/article/details/7767829

你猜的不錯,addFont()方法就不支援從Jar包中獲取資原始檔!!!

其實,render.getFontResolver().addFont()這個方法的底層是BaseFont.createFont()實現的,原始碼如下:

 然後我們再來看看BaseFont的createFont()的底層實現:(原始碼一時找不到了,找到了會補充進來~)

但是,這個方法是通過File f = new File(path)實現的。所以無法從jar包獲取資源

針對這個問題,我個人提出了三種解決方案:

1.使用原生的itext5來寫html檔案,其中可以利用中文支援包iText-asian來引入字型,從而解決中文問題。

BaseFont bfChinese = BaseFont.createFont( "STSongStd-Light" ,"UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);

但需要注意:flying sauce是基於iText2.1.7,低版本的iText-asian包和iText2.1.7會有包名衝突。需要改包名。

2.jar包中的資源是可以被讀取的,比如說利用流的形式。思路為:先從Jar包中獲取資原始檔,寫入存到專案檔案中,然後通過專案檔案路徑獲取資源。

3.在flying sauce中,字型處理器ITextFontResolver的父類FontResolver,其還有另一個實現類:AWTFontResolver。嘗試繼承這個類並重寫以位元組流的形式讀取路徑檔案,再通過render.getSharedContext().setFontResolver(),強制轉換一波應該可以實現。目前還在實驗中~有興趣的同學可以加我QQ(714635093)一起討論~~~