由於業務需求要根據合同模板和線上的數據實時生成合同,避免客戶打印了模板還要去填數據的問題須要導出PDF,發現了IText這個東西。下面就簡單的介紹一下一些簡單的使用和一些常見的問題css
咱們能夠建立讓用戶本身建立模板,模板是使用Freemarker存儲的,導出PDF的時候就是取數據——>渲染Freemarker模板——>導出PDF。html
最經常使用的就是Paragraph和PdfTable了,這個難度都不大,隨便搜索一下都能找到不少資料和實例。java
這裏咱們強調2個問題:spring
中文問題也很好解決設置BaseFont就能夠了。有一些須要使用FontProvider實現以下:apache
static class MyFontProvide implements FontProvider { @Override public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) { BaseFont bFont = null; try { bFont = createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); //bFont = BaseFont.createFont(fontname, encoding, embedded); } catch (Exception e) { e.printStackTrace(); } //return new Font(bFont, 8, Font.NORMAL); return new Font(bFont,size,style,color); } @Override public boolean isRegistered(String arg0) { return false; } }
具體的更多的能夠看BaseFont類。api
不少要添加水印都只添加了第一頁,爲了解決這個問題可使用PdfPageEvent,下面來一個實現看一下:安全
static class MyPdfPageEvent implements PdfPageEvent{ @Override public void onStartPage(PdfWriter writer, Document document) { PdfGState gs = new PdfGState(); gs.setFillOpacity(0.5f); gs.setStrokeOpacity(0.2f); String waterMarkName = "合同預覽"; PdfContentByte under = writer.getDirectContentUnder(); under.beginText(); under.setColorFill(BaseColor.LIGHT_GRAY); under.setFontAndSize(baseFont, 100); under.setTextMatrix(0, 0); under.setGState(gs); under.showTextAligned(Element.ALIGN_CENTER,waterMarkName,200,200,45); under.setTextMatrix(50, 50); under.showTextAligned(Element.ALIGN_CENTER,waterMarkName,400,400,-45); under.endText(); } @Override public void onOpenDocument(PdfWriter writer, Document document) { } @Override public void onEndPage(PdfWriter writer, Document document) { } @Override public void onCloseDocument(PdfWriter writer, Document document) { } @Override public void onParagraph(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onParagraphEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title) { } @Override public void onChapterEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title) { } @Override public void onSectionEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) { } }
能夠看到有不少事件,咱們只須要在開始新的一頁的時候添加水印就能夠了。網絡
import com.itextpdf.text.*; import com.itextpdf.text.pdf.*; import com.itextpdf.tool.xml.XMLWorkerHelper; import freemarker.template.Configuration; import freemarker.template.Template; import org.apache.commons.io.IOUtils; import org.junit.Test; import java.io.*; import java.util.HashMap; import java.util.Map; import java.util.UUID; import static com.itextpdf.text.pdf.BaseFont.createFont; import static freemarker.template.Configuration.VERSION_2_3_23; public class ContractHtmlPdfTest { private static BaseFont baseFont; static { try { baseFont = createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } ; public static String getHtml(String templateStr, Map<String, Object> root) throws Exception { Configuration config=new Configuration(VERSION_2_3_23); Template template= new Template("htmlTemplate", new StringReader(templateStr), config); StringWriter writer = new StringWriter(); template.process(root,writer); return writer.toString(); } @Test public void htmlToPdf() throws Exception { Document document = new Document(); FileOutputStream fos = new FileOutputStream("F:\\tmp\\contract.pdf"); PdfWriter writer = PdfWriter.getInstance(document, fos); writer.setPageEvent(new MyPdfPageEvent());//添加水印 InputStream contractHtml = ContractHtmlPdfTest.class.getResourceAsStream("/contract.html"); int len = 1024 * 1024; byte [] buffer = new byte[len]; int read = IOUtils.read(contractHtml, buffer); byte [] cb = new byte[read]; System.arraycopy(buffer,0,cb,0,read); String html = new String(cb); // System.out.println(html); String rs = getHtml(html, getMap()); // System.out.println(rs); ByteArrayInputStream bais = new ByteArrayInputStream(rs.getBytes()); InputStream contractCss = ContractHtmlPdfTest.class.getResourceAsStream("/contract.css"); document.open(); XMLWorkerHelper xmlWorkerHelper = XMLWorkerHelper.getInstance(); xmlWorkerHelper.parseXHtml(writer, document, bais,contractCss,new MyFontProvide()); document.add(getPdfTable()); document.close(); } private Map<String,Object> getMap(){ HashMap<String, Object> result = new HashMap<>(); result.put("firstPartyName","xx有限責任公司"); result.put("firstPartyAddr","上海"); result.put("firstPartyContact","李四"); result.put("firstPartyPhone","13888888888"); result.put("secondPartyName","YY有限責任公司"); result.put("secondPartyAddr","北京"); result.put("secondPartyContact","張三"); result.put("secondPartyPhone","13877777777"); return result; } private PdfPTable getPdfTable(){ //建立一個有3列的表格 PdfPTable table = new PdfPTable(3); table.setWidthPercentage(100); table.setHorizontalAlignment(Element.ALIGN_CENTER); PdfPCell cell = new PdfPCell(new Paragraph("Title")); //合併3個單元格 cell.setColspan(3); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); for(int i = 0;i<9;i++) { table.addCell(UUID.randomUUID().toString().substring(0,8)); } cell = new PdfPCell(new Paragraph("setBorderColor")); cell.setBorderColor(BaseColor.BLUE); table.addCell(cell); cell = new PdfPCell(new Paragraph("setBackgroundColor")); cell.setColspan(2); cell.setBackgroundColor(BaseColor.GRAY); table.addCell(cell); return table; } static class MyFontProvide implements FontProvider { @Override public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) { BaseFont bFont = null; try { bFont = createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); // bFont = BaseFont.createFont(fontname, encoding, embedded); } catch (Exception e) { e.printStackTrace(); } // return new Font(bFont, 8, Font.NORMAL); return new Font(bFont,size,style,color); } @Override public boolean isRegistered(String arg0) { return false; } } static class MyPdfPageEvent implements PdfPageEvent{ @Override public void onStartPage(PdfWriter writer, Document document) { PdfGState gs = new PdfGState(); gs.setFillOpacity(0.5f); gs.setStrokeOpacity(0.2f); String waterMarkName = "合同預覽"; PdfContentByte under = writer.getDirectContentUnder(); under.beginText(); under.setColorFill(BaseColor.LIGHT_GRAY); under.setFontAndSize(baseFont, 100); under.setTextMatrix(0, 0); under.setGState(gs); under.showTextAligned(Element.ALIGN_CENTER,waterMarkName,200,200,45); under.setTextMatrix(50, 50); under.showTextAligned(Element.ALIGN_CENTER,waterMarkName,400,400,-45); under.endText(); } @Override public void onOpenDocument(PdfWriter writer, Document document) { } @Override public void onEndPage(PdfWriter writer, Document document) { } @Override public void onCloseDocument(PdfWriter writer, Document document) { } @Override public void onParagraph(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onParagraphEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title) { } @Override public void onChapterEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title) { } @Override public void onSectionEnd(PdfWriter writer, Document document, float paragraphPosition) { } @Override public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) { } } }
後面有測試中用到的文件,放在項目的classpath就能夠了。dom
生成的PDF截圖大概是這樣的:maven
<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>contract</title> <link rel="stylesheet" type="text/css" href="contract.css" /> </head> <body> <div id="title">電影拍攝合做協議</div> <div class="subjectWrap"> <div class="subject">甲方: ${firstPartyName} </div> <div class="subject">註冊地址:${firstPartyAddr} </div> <div class="subject">聯繫人: ${firstPartyContact}</div> <div class="subject">聯繫電話:${firstPartyPhone} </div> </div> <div class="subjectWrap"> <div class="subject">乙方: ${secondPartyName} </div> <div class="subject">註冊地址:${secondPartyAddr} </div> <div class="subject">聯繫人: ${secondPartyContact}</div> <div class="subject">聯繫電話: ${secondPartyPhone} </div> </div> <div id="content"> 風險提示:<br /><br />合做的方式多種多樣,如合做設立公司、合做開發軟件、合做購銷產品等等,不一樣合做方式涉及到不一樣的項目內容,相應的協議條款可能大不相同。<br /><br />本協議的條款設置創建在特定項目的基礎上,僅供參考。實踐中,須要根據雙方實際的合做方式、項目內容、權利義務等,修改或從新擬定條款。<br /><br />雙方經友好協商,就共同投資攝製一部放映時間爲xxxx分鐘的xxxx毫米彩色故事片《xxxx》,達成以下協議:<br /><br />第一條、劇本和生產許可<br /><br />一、本片劇本由乙方提供,經甲、乙雙方贊成並獲中國電影行政主管部門批准拍攝。<br /><br />二、雙方一致贊成,本片經中國電影行政主管部門批准立項後,將嚴格按照審查經過的劇本拍攝,如要對劇本進行情節變動,需徵得雙方贊成,並經中國電影合做製片公司報送中國電影行政主管部門從新審批。<br /><br />三、甲方承擔並辦理與本片有關的合拍申請、報批影片送審、取得放映許可證的相關手續。審查影片及取得發行放映許可證的費用由甲方承擔並預先支付(僅包括管理部門按正式文件規定收取的劇本審查、節目立項、雙片審查費用,必須提供有效證實)。<br /><br />四、本片在藝術處理上應符合中國國情,尊重中華民族的風俗習慣。<br /><br />第二條、預算、投資和投資比例及投資方式<br /><br />一、本片由甲、乙雙方共同投資聯合拍攝,投資拍攝資金爲xxxx元,投資比例爲甲方xxxx元(大寫:xxxx),乙方xxxx元(大寫:xxxx)。<br /><br />二、以上投資款,支付至雙方指定帳戶:xxxx,開戶行:xxxx,帳號:xxxx。<br /><br />三、合同簽定後xxxx個工做日內,雙方即付定金xxxx元(大寫:xxxx)。若是一方未能按時支付定金,另外一方有權單方面終止本合同書,並與第三方簽定相似合同書。<br /><br />四、項目肯定、《拍攝許可證》取得後,開機前xx周內,甲、乙雙方即付清投資餘款,不然,視爲違約,守約方有權向違約方要求賠償損失。<br /><br />第三條、攝製組人員組成<br /><br />一、本片攝製組由甲、乙雙方共同組成。主創人員名單應在本片開機前由甲方報中國電影合做製片公司並經中國電影主管部門贊成。<br /><br />二、雙方商定本片出品人甲方爲xxxx,乙方爲xxxx。<br /><br />三、本片導演爲xxxx。<br /><br />四、本片演員均由乙方指定並提供,經甲方贊成,並按規定程序將有關資料報批。<br /><br />五、甲方指定xxxx爲該片項目負責人。項目負責人表明甲方協調處理有關事項,監督進出關及影片拍攝狀況,酬金享受本片製片待遇(甲方詳列工做內容及薪酬標準)。他們在協助協調本片拍攝的各項工做所發生的食、宿、交通、電訊等費用由攝製組承擔,實報實銷。<br /><br />六、攝製組全體人員、設備及重要拍攝場地的安全進行保險,保險費由攝製組承擔。<br /><br />第四條、拍攝和後期製做<br /><br />一、本片的拍攝週期,甲、乙雙方商定自xxxx年xxxxx月至xxxx年xxxx月底完成。<br /><br />(1)開機時間:xxxx。<br /><br />(2)停機時間:xxxx。<br /><br />(3)雙片送審時間:xxxx。<br /><br />(4)出片時間:xxx。<br /><br />二、本片的拍攝地點爲xxx。如需移至他地拍攝,甲、乙雙方應及時通知中國電影合做製片公司。<br /><br />三、本片的底、樣片沖印及後期製做應在中國內地完成。如因技術等特殊緣由,需在境外進行部分後期製做,應由雙方提出申請,經中制公司報送中國電影行政主管部門批准。<br /><br />四、本片在中國內地拍攝期間,攝製組人員應遵照中華人民共和國法律、法規,尊重拍攝地風俗習慣。<br /><br />第五條、影片送審<br /><br />一、本片的混錄雙片完成後,經甲、乙雙方審定,由甲方負責送中國電影合做製片公司和國家廣電總局電影審查委員會審查,並嚴格遵守審查修改意見進行修改。<br /><br />二、本片經國家廣電總局電影審查委員會審查經過並取得《電影片公映許可證》後,方可在中國境內外發行放映、銷售或以任何其餘方式在公衆領域進行映出或作商業性推銷傳播活動。<br /><br />三、本片經國家廣電總局電影審查經過後爲該片的惟一版本。本片取得《電影片公映許可證》後,任何一方不得擅自修改,製做其它版本,若有情節變動、名稱變動,應當從新向中國電影合做製片公司和中國電影行政主管部門報審。<br /><br />第六條、版權和發行收益<br /><br />一、本片以及與本片有關的全部載體的著做權歸甲、乙雙方共同擁有。<br /><br />二、本片中國內地的院線發行權、電視播映權及其收益歸甲方全部。包括:(可選擇)在中國內地(除香港、澳門、臺灣地 區外)音像權、35mm、16mm、數字電影,電影頻道,有線電視,無線電視、網絡傳輸的發行權)。其它全部權均爲乙方全部。包括:(可選擇)港、澳、臺 地區及海外全部權(家庭影院、電影發行、有線及無線電視播映、網絡傳輸等全部權)及收益。家庭影院:指錄像帶、ld、vcd、dvd、evd等以及未來發 明的一切家庭式播放錄像產品。<br /><br />三、除前款規定外,因本片或因行使本權利而產生的其餘一切權利,按雙方投資比例分紅享有;雙方全部廣告收益歸各方全部。<br /><br />四、上述權限期限爲2019-01-01。<br /><br />第七條、發行銷售<br /><br />一、 本片發行後送交兩部標準拷貝(中國電影行政主管部門一部,中國電影資料館一部)和大1/2錄像帶四盤(中國電影行政主管部門三盤,中國電影合做製片公司一 盤),及向國家廣電總局審查委員會上繳betacam影片節目帶一盤。爲保證投資方的權益,上繳的拷貝錄像帶和betacam影片節目帶可印上「樣帶」字 樣;此項資料拷貝費用由攝製組承擔。<br /><br />二、甲、乙方如出售、轉讓本片的發行權,則必須在其合同中註明:所出售、轉讓的權利不包括選送本片參加各種國際(地區)電影節、電影展等活動。<br /><br />三、 本片片頭字幕的排列以及本片的一切副產品的片頭字幕的排列(包括有關本片的包裝及文字印刷品之上的說明等),若在中國內地發行銷售,應爲甲方在前,乙方在 後;若在中國內地之外的國家或地區(含港、澳、臺地區)發行銷售,應爲乙方在前,甲方在後;根據中國電影行政主管部門規定「中國電影合做製片公司協助拍 攝」應以獨立畫幅排列在甲乙或乙甲兩方以後。不管影片在什麼時候、何地發行、放映、參加國際(地區)電影節、展,影片字幕署名不得變動、刪減。<br /><br />四、乙方贊成2018-08-08向甲方提供本片故事梗概、演職員資料、劇照、海報等宣傳資料,做爲甲方存檔和電影發行之用。<br /><br />第八條、報關和簽證<br /><br />一、 根據中國《電影管理條例》規定,爲拍攝本片所需臨時入出境的設備、器材和膠片等物品的報關手續由甲方指派具備表明甲方資格的專職人員負責辦理。該人員職責 應從設備、器材和膠片等物品入境之日起至所有出境物品結關手續辦理結束之日止。乙方攝製人員以及受聘人員的入境簽證手續,由甲方代爲辦理。<br /><br />二、乙方應如實向中國海關申報進出境的一切物品,若發現申報不實,乙方應對此負責;若由上述緣由而形成甲方經濟損失,乙方應給予賠償。<br /><br />三、爲本片辦理器材、膠片等物品臨時進、出境海關手續所發生的包裝、儲運、報關費用由攝製組承擔;報關人員的食、宿、交通、電訊、勞務、補助等費用均由攝製組承擔。<br /><br />四、乙方爲拍攝本片臨時入境的設備、器材和膠片等物品,在本片拍攝結束後,應按中國海關的有關規定在5個月以內(自入境之日起)所有復運出境。逾期不出者,海關所以而收取的一切稅、費由乙方繳納。<br /><br />第九條、參賽參展和獲獎<br /><br />一、經中國電影合做製片公司報中國電影行政主管部門批准,甲、乙雙方憑本片可參加各種國內外電影節、展等活動,其表明團成員的活動經費由甲、乙方各自負責。<br /><br />二、 本片獲獎榮譽雙方共享。在中國內地若獲影片獎,獎品和獲獎證書原件歸甲方,獎金分配原則爲甲方佔50%,乙方佔50%。在中國內地之外(含港、澳地區)獲影片獎,獎金、獎品、獲獎證書歸甲方,向另外一方提供獎品和獲獎證書複製品。若獲個 人單項獎的獎金,獎品和證書歸獲獎者我的全部。<br /><br />第十條、不可抗力<br /><br />本片在攝製過程當中,如遇不可抗力的因素而形成攝製工做暫停或終止,甲、乙雙方應及時將狀況通告中國電影合做公司。<br /><br />第十一條、違約責任<br /><br />風險提示:<br /><br />合同的約定雖然細緻,但沒法保證合做方不違約。所以,必須明確約定違約條款,一旦一方違約,另外一方則可以以此做爲追償依據。<br /><br />一、 本合同任何一方在合同簽字後不執行或違反本合同第一條第1、2、四款;第三條第1、二款;第四條第三款;第五條第三款;第六條第一款;第七條第4、五款; 第九條第一款之規定,或將本合同擅自轉讓給任何非本合同簽約方,視爲該方對本合同的重大違約。違約方應承擔違約責任,並接受中國電影行政主管部門根據中國 《電影管理條例》對違約方做出處罰。<br /><br />二、若本合同任何一方違約而形成另外一方經濟損失,受損失方有權追究違約方的責任並提出賠償要求。<br /><br />三、違約方的損失賠償額應當至關於因違約形成的損失,包括合同履行後能夠得到的利益。<br /><br />第十二條、法律適用和管轄<br /><br />一、本合同以及構成本合同組成部分的附件的解釋、效力和補充,均應適用中華人民共和國法律。<br /><br />二、全部因履行本合同或與本合同有關的爭議,應由中華人民共和國有管轄權的法院管轄並依該管轄法院適用的程序法加以解決。<br /><br />三、全部因履行本合同義務或合同有關的一切爭議,應首先由爭議各方經過友好協商加以解決;若爭議各方沒法提出解決方案並達成協議。<br /><br /><br />第十三條、補充協議<br /><br />本合同若有未盡事宜,雙方應友好協商解決,必要時另行簽定補充協議書。補充協議與本合同具備同等法律效力。<br /><br />第十四條、生效和終止<br /><br />一、本合同由甲、乙雙方法定表明簽字,自簽字之日起生效。<br /><br />二、本合同以中文寫成,一式2份,雙方各執1份,具備同等的權威和效力。 </div> <div id="signature"> <div id="first"> <div class="subject">甲方: ${firstPartyName}</div> <div class="subject">受權表明:</div> <div class="subject">日期: </div> </div> <div id="second"> <div class="subject">乙方: ${secondPartyName}</div> <div class="subject">受權表明:</div> <div class="subject">日期: </div> </div> </div> </body> </html>
#first{ float: left; width: 250px; height:200px; margin-top: 100px; } #second{ float: right; margin-top: 100px; width: 250px; height:200px; /*margin-right: 40%;*/ margin-right: 700px; } #title{ text-align: center; font-size: 35px; margin-bottom: 20px; padding: 20px; } .subjectWrap{ margin-bottom: 20px; width: 400px; height: 135px; } .subject{ display: block; padding: 4px; } #content{ /*line-height: 35px;*/ } table,th,td{ border: 1px solid #a0c6e5; border-collapse:collapse; }
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.freemethod</groupId> <artifactId>ipdf</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>ipdf</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.6.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.4.3</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.4.1</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.11</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.2</version> </dependency> <!--test--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.2.13.RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>