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)一起討論~~~