最近項目有一個導出報表文件的需求,我腦中閃過第一念頭就是導出pdf(產品經理沒有硬性規定導出excel仍是pdf文件),因而趕忙上網查看相關的資料,直到踩了無數的坑把功能作出來了才知道其實導出excel的api更方便,網上的相關博客也更多,不過坑也踩完了,這一次就來把代碼和收穫整理和分享一下。java
在看到需求以前,我先去網上去搜了一波,發現網上很大一部分博客都是將如何導出pdf模板的,就是先製做一張pdf模板,把固定不變的地方先寫好,把須要改變的地方留白並設置參數,而後在代碼裏爲參數賦值就好了,這種方式很簡單,代碼量也不多,可是!!!這種方式只適合導出格式和內容是固定的文件,而咱們的需求是導出一張以產品名稱爲列,產品屬性爲行的報表,產品數量不定,這很顯然就不能用模板的方式,只好老老實實把報表的數據從頭至尾一一導出來。linux
iText是一種生成PDF報表的Java組件,先把jar包下下來,maven依賴以下:web
<dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.0.6</version> </dependency>
按照慣例,先來生一個「Hello World」的文件,代碼以下spring
public class TestPdf { public static void main(String[] args) throws Exception { TestPdf pdf = new TestPdf(); String filename = "D:/Program Files/pdfTest/testTable3.pdf"; pdf.createPDF(filename); System.out.println("打印完成"); } public void createPDF(String filename) throws IOException { Document document = new Document(PageSize.A4); try { PdfWriter.getInstance(document, new FileOutputStream(filename)); document.addTitle("example of PDF"); document.open(); document.add(new Paragraph("Hello World!")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } finally { document.close(); } } }
這個沒什麼可說的,照着寫就好了,不過咱們導出的pdf大多數是以表格的形式,因此我就直接切入主題了,這裏要用到一個很關鍵的類com.itextpdf.text.pdf.PDFPTable,先導出一張兩行兩列的表格windows
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 15; cell = new PdfPCell(new Phrase("one")); cell.setFixedHeight(size);//設置高度 table.addCell(cell); cell = new PdfPCell(new Phrase("two")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("three")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("four")); cell.setFixedHeight(size); table.addCell(cell); return table; } public void createPDF(String filename) throws IOException { Document document = new Document(PageSize.A4); try { PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename)); document.addTitle("example of PDF"); document.open(); PdfPTable table = createTable(writer); document.add(table); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } finally { document.close(); } }
效果以下圖api
如今把咱們的需求變得更苛刻一點,再增長一行,格子數爲1,文字水平和垂直居中對齊,佔兩行高度,先貼代碼再作解釋瀏覽器
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 15; cell = new PdfPCell(new Phrase("one")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("two")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("three")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("four")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("five")); cell.setColspan(2);//設置所佔列數 cell.setFixedHeight(size*2);//設置高度 cell.setHorizontalAlignment(Element.ALIGN_CENTER);//設置水平居中 cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//設置垂直居中 table.addCell(cell); return table; }
這裏有一個很坑的地方,就是每一行的長度要和初始化表格的長度相等,即要把整行給佔滿,不然後面的都不會打印出來,因此要用到setColspan()方法來合併列,接下來還有一個就是合併行的的方法setRowspan()。咱們在導出pdf報表的實際操做中常常會有這樣的需求springboot
因此合併行的方法顯得尤其重要,那就嘗試一下用這個方法來合併行,服務器
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 20; cell = new PdfPCell(new Phrase("one")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("two")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("three")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("four")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("five")); cell.setColspan(1);//設置所佔列數 cell.setRowspan(2);//合併行 cell.setFixedHeight(size*2);//設置高度 cell.setHorizontalAlignment(Element.ALIGN_CENTER);//設置水平居中 cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//設置垂直居中 table.addCell(cell); cell = new PdfPCell(new Phrase("six")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("seven")); cell.setFixedHeight(size); table.addCell(cell); return table; }
效果以下app
事實上很簡單,在原來的代碼上將「five」這一行長度由2變爲1,加上setRowspan(2)方法,再加上兩行長度爲1的cell就是上面的效果了。看起來也沒有多複雜是吧,如今咱們在jar包上用了較新的版本itextpdf-5.0.6.jar,然而在這以前還有一種老版本的jar包itext-2.1.7.jar,依賴以下:
<dependency> <groupId>com.lowagie</groupId> <artifactId>itext</artifactId> <version>2.1.7</version> </dependency>
用了這個jar包後發現效果是同樣的,那我爲何要提這個版本呢,在後面咱們會講打到。事實上,在實現行合併的還有一種方法就是table中再套一個table,代碼以下:
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 20; cell = new PdfPCell(new Phrase("one")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("two")); cell.setFixedHeight(size); cell.setColspan(2); table.addCell(cell); cell = new PdfPCell(new Phrase("three")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("four")); cell.setFixedHeight(size); cell.setColspan(2); table.addCell(cell); cell = new PdfPCell(new Phrase("five")); cell.setColspan(1);//設置所佔列數 //cell.setRowspan(2);//合併行 cell.setFixedHeight(size*2);//設置高度 cell.setHorizontalAlignment(Element.ALIGN_CENTER);//設置水平居中 cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//設置垂直居中 table.addCell(cell); PdfPTable table2 = new PdfPTable(1);//新建一個table cell = new PdfPCell(new Phrase("six")); cell.setFixedHeight(size); table2.addCell(cell); cell = new PdfPCell(new Phrase("seven")); cell.setFixedHeight(size); table2.addCell(cell); cell = new PdfPCell(table2);//將table放到cell中 table.addCell(cell);//將cell放到外層的table中去 return table; }
效果和剛纔是徹底同樣的,這種方法的關鍵就是將新建的table放到cell中,而後把cell放到外層table中去。這種方法明顯比剛纔的麻煩不少,我之因此拿出來是由於我在用老版本的jar包中的合併行的方法忽然不起做用了,而後臨時想了這個辦法出來,至於爲何方法失效報錯我到如今還沒弄明白。。。
之因此提這個,在我本身項目中就有這樣的需求,並且在網上這種資料很是少,我這也是在官網上看到的,遂記錄一下,先看一下單選框怎麼加,代碼以下
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 20; cell = new PdfPCell(new Phrase("one")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("two")); cell.setFixedHeight(size); cell.setColspan(2); table.addCell(cell); cell = new PdfPCell(new Phrase("three")); cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase("four")); cell.setFixedHeight(size); cell.setColspan(2); table.addCell(cell); cell = new PdfPCell(new Phrase("five")); cell.setColspan(1);//設置所佔列數 //cell.setRowspan(2);//合併行 cell.setFixedHeight(size*2);//設置高度 cell.setHorizontalAlignment(Element.ALIGN_CENTER);//設置水平居中 cell.setVerticalAlignment(Element.ALIGN_MIDDLE);//設置垂直居中 table.addCell(cell); PdfPTable table2 = new PdfPTable(1);//新建一個table cell = new PdfPCell(new Phrase("six")); cell.setFixedHeight(size); table2.addCell(cell); cell = new PdfPCell(new Phrase("seven")); cell.setFixedHeight(size); table2.addCell(cell); cell = new PdfPCell(table2);//將table放到cell中 table.addCell(cell);//將cell放到外面的table中去 Phrase phrase1 = new Phrase(); Chunk chunk1 = new Chunk(" YES"); Chunk chunk2 = new Chunk(" NO"); phrase1.add(chunk1); phrase1.add(chunk2); cell = new PdfPCell(phrase1); cell.setColspan(2); table.addCell(cell); //增長兩個單選框 PdfFormField radiogroup=PdfFormField.createRadioButton(writer, true); radiogroup.setFieldName("salesModel"); Rectangle rect1 = new Rectangle(110, 722, 120, 712); Rectangle rect2 = new Rectangle(165, 722, 175, 712); RadioCheckField radio1 = new RadioCheckField(writer, rect1, null, "self-support" ) ; RadioCheckField radio2 = new RadioCheckField(writer, rect2, null, "cooprate") ; radio2.setChecked(true); PdfFormField radiofield1 = radio1.getRadioField(); PdfFormField radiofield2 = radio2.getRadioField(); radiogroup.addKid(radiofield1); radiogroup.addKid(radiofield2); writer.addAnnotation(radiogroup); return table; }
效果如圖
這個最煩的地方就是本身要去一點點試單選框的參數來調整位置,就是 Rectangle rect1 = new Rectangle()裏面的參數,而後想讓哪一個radio被選中就用setChecked(true)方法就OK了
在以前的例子中我用的都是英文字,可是中文字體是一個不得不解決的問題,根據我去網上搜集到的資料大抵有如下幾個方法:
public static PdfPTable createTable(PdfWriter writer) throws DocumentException, IOException{ PdfPTable table = new PdfPTable(2);//生成一個兩列的表格 PdfPCell cell; int size = 20; Font font = new Font(BaseFont.createFont("C:/Windows/Fonts/SIMYOU.TTF",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED)); cell = new PdfPCell(new Phrase("顯示中文",font)); cell.setFixedHeight(size); cell.setColspan(2); table.addCell(cell); return table; }
Font font = new Font(BaseFont.createFont( "STSongStd-Light" ,"UniGB-UCS2-H",BaseFont.NOT_EMBEDDED));
在這裏可能會報Font 'STSongStd-Light' with 'UniGB-UCS2-H' is not recognized.的錯,網上都說是語言包中的包名有問題,可是我把itexitextpdf包的版本從5.0.6改爲5.4.3就解決了,我把語言包解壓後發現路徑是對的,應該是後面的版本已經把語言包中的包名解決了,那爲何5.0.6的版本不行呢,反正我到如今還沒找到緣由,若是有誰知道的話歡迎留言告知。
3.使用本身下載的資源字體。這種方法能夠說是最麻煩的,可是也是最有效的。麻煩是由於本身要去下載字體文件,可是效果確是最好的,可以應對各類系統,各類情況,下面會提到,先看一下個人文件路徑
而後是代碼
Font font = new Font(BaseFont.createFont( "/simsun.ttf",BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED));
其實跟第一個是差很少的,只是一個是讀系統的文件,一個是讀項目本身的文件。
前面作了這麼多鋪墊,終於來到咱們最關鍵的地方了,前面的東西都是放到java項目中去跑的,沒有太多實際用處,在web項目中用到纔算真正的應用,假設咱們有一個產品類,咱們的需求就是把產品數據導成pdf文件,代碼以下:
產品Product類
public class Product { private String productName; private String productCode; private float price; public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public String getProductCode() { return productCode; } public void setProductCode(String productCode) { this.productCode = productCode; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public Product(String productName, String productCode, float price) { super(); this.productName = productName; this.productCode = productCode; this.price = price; } }
接下來是controller
//打印pdf @RequestMapping("printPdf") public ModelAndView printPdf() throws Exception{ Product product1 = new Product("產品一","cp01",120); Product product2 = new Product("產品一","cp01",120); Product product3 = new Product("產品一","cp01",120); List<Product>products = new ArrayList<>(); products.add(product1); products.add(product2); products.add(product3); Map<String, Object> model = new HashMap<>(); model.put("sheet", products); return new ModelAndView(new ViewPDF(), model); }
而後是ViewPDF類
public class ViewPDF extends AbstractPdfView { @Override protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { String fileName = new Date().getTime()+"_quotation.pdf"; // 設置response方式,使執行此controller時候自動出現下載頁面,而非直接使用excel打開 response.setCharacterEncoding("UTF-8"); response.setContentType("application/pdf"); response.setHeader("Content-Disposition","filename=" + new String(fileName.getBytes(), "iso8859-1")); List<Product> products = (List<Product>) model.get("sheet"); PdfUtil pdfUtil = new PdfUtil(); pdfUtil.createPDF(document, writer, products); } }
在這裏有兩個值得注意的地方:
1. 仔細觀察這個類,看父類的bulidPdfDocument(),發現其中的document要求的是這個com.lowagie.text.Document版本的,這就很是坑了,意味着以前的itextpdf-5.0.6.jar不能再用了,只能用老版本的(更名以前的)itext-2.1.7.jar了。
2. 若是不想要這種直接在瀏覽器中預覽的效果,而是直接下載文件的話就把response.setHeader()方法裏面的參數改爲下面的,至關於加了一個attachment,以附件形式下載
response.setHeader("Content-Disposition","attachment;filename=" + new String(fileName.getBytes(), "iso8859-1"));
最後是PdfUtil類
public class PdfUtil { public void createPDF(Document document,PdfWriter writer, List<Product> products) throws IOException { //Document document = new Document(PageSize.A4); try { document.addTitle("sheet of product"); document.addAuthor("scurry"); document.addSubject("product sheet."); document.addKeywords("product."); document.open(); PdfPTable table = createTable(writer,products); document.add(table); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } finally { document.close(); } } public static PdfPTable createTable(PdfWriter writer,List<Product> products) throws IOException, DocumentException { PdfPTable table = new PdfPTable(3);//生成一個兩列的表格 PdfPCell cell; int size = 20; Font font = new Font(BaseFont.createFont("C://Windows//Fonts//simfang.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED)); for(int i = 0;i<products.size();i++) { cell = new PdfPCell(new Phrase(products.get(i).getProductCode(),font));//產品編號 cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase(products.get(i).getProductName(),font));//產品名稱 cell.setFixedHeight(size); table.addCell(cell); cell = new PdfPCell(new Phrase(products.get(i).getPrice()+"",font));//產品價格 cell.setFixedHeight(size); table.addCell(cell); } return table; } }
這同時也意味着若是項目要打包部署到linux服務器上去的話,前兩種的中文解決辦法都很差使了,只能下載中文字體資源文件,然而這其中又有一個坑,打包後文件路徑讀取不到。
因此要以加載資源文件的形式讀取文件,代碼以下
BaseFont baseFont = BaseFont.createFont(new ClassPathResource("/simsun.ttf").getPath(), BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
效果以下圖:
在這裏既能夠下載也能夠打印。
到這裏我這踩了一路的坑都已經說完了,若是以後遇到新的問題我也持續更新這篇博客的。總的來講導出pdf真的是一件很繁瑣的事情,仍是那句話若是能導excel的話就儘可能導excel吧。。。