1. 程式人生 > 實用技巧 >java通過html生成pdf,支援css和圖片以及橫向列印

java通過html生成pdf,支援css和圖片以及橫向列印

專案當中通常會有生成pdf的需求,pdf的排版尤為重要!通過html生成,最為方便.

1. 依賴

工具使用freemarker模板進行資料渲染

<dependency>
  <groupId>org.freemarker</groupId>
  <artifactId>freemarker</artifactId>
  <version>2.3.29</version>
</dependency>
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.4</version>
</dependency>
<dependency>
  <groupId>org.xhtmlrenderer</groupId>
  <artifactId>flying-saucer-pdf</artifactId>
  <version>9.1.18</version>
</dependency>

2. 工具類

import java.io.*;
import java.util.Locale;
import java.util.Map;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;

public class PdfUtil {

	/**
	 * 通過模板匯出pdf檔案
	 * @param data 資料
	 * @param templateFileName 模板檔名
	 * @throws Exception
	 */
    public static ByteArrayOutputStream createPDF(Map<String,Object> data, String templateFileName) throws Exception {
        // 建立一個FreeMarker例項, 負責管理FreeMarker模板的Configuration例項
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板檔案的位置 
        cfg.setClassForTemplateLoading(PdfUtil.class,"/templates");
        ITextRenderer renderer = new ITextRenderer();
        OutputStream out = new ByteArrayOutputStream();
        try {
            // 設定 css中 的字型樣式(暫時僅支援宋體和黑體) 必須,不然中文不顯示
            renderer.getFontResolver().addFont("/static/font/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            // 設定模板的編碼格式
            cfg.setEncoding(Locale.CHINA, "UTF-8");
            // 獲取模板檔案 
            Template template = cfg.getTemplate(templateFileName, "UTF-8");
            StringWriter writer = new StringWriter();
            
            // 將資料輸出到html中
            template.process(data, writer);
            writer.flush();

            String html = writer.toString();
            // 把html程式碼傳入渲染器中
            renderer.setDocumentFromString(html);

             // 設定模板中的圖片路徑 (這裡的images在resources目錄下) 模板中img標籤src路徑需要相對路徑加圖片名 如<img src="images/xh.jpg"/>
            String url = PdfUtil.class.getClassLoader().getResource("static/images").toURI().toString();
            renderer.getSharedContext().setBaseURL(url);
            renderer.layout();
            
            renderer.createPDF(out, false);
            renderer.finishPDF();
            out.flush();
            return (ByteArrayOutputStream)out;
        } finally {
        	if(out != null){
        		 out.close();
        	}
        }
    }
}

程式碼中需要注意路徑設定,否則會導致css和圖片引入無效

  • cfg.setClassForTemplateLoading(PdfUtil.class,"/templates"); 指定FreeMarker模板檔案的位置

  • renderer.getFontResolver().addFont("/static/font/SIMSUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); 指定字型檔案,否則中文不顯示

  • PdfUtil.class.getClassLoader().getResource("static/images").toURI().toString();

    指定模板中圖片路徑

宋體字型下載: https://dl.pconline.com.cn/download/367689-1.html

靜態資源目錄結構:

3. 使用

建議使用時,先寫一個html靜態頁面,除錯好了再複製到ftl檔案中,儲存成模板

靜態index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title></title>
    <style>
        .center{
            width: 380px;
            height: 538px;
            background: url("images/zs.png") center no-repeat;
            margin: 10% auto;
            font-family: SimSun;
            position: relative;
        }
        .name{
            position: absolute;
            top: 216px;
            left: 60px;
            font-size: 20px;
            width: 74px;
            text-align: center;
            display: block;
        }
    </style>
</head>
<body>
<div class="center">
    <span class="name">李逍遙</span>
</div>
</body>
</html>

模板zhengshu.ftl

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title></title>
    <style>
        .center{
            width: 380px;
            height: 538px;
            background: url("images/zs.png") center no-repeat;
            margin: 15% auto;
            font-family: SimSun;
            position: relative;
        }
        .name{
            position: absolute;
            top: 216px;
            left: 60px;
            font-size: 20px;
            width: 74px;
            text-align: center;
            display: block;
        }
    </style>
</head>
<body>
<div class="center">
    <span class="name">${name}</span>
</div>
</body>
</html>

把需要設定資料的地方,用freemarker語法進行佔位${}

單元測試

@Test
public void pdf() throws IOException {
    ByteArrayOutputStream baos = null;
    FileOutputStream out = null;
    try {
        Map<String,Object> data = new HashMap<>();
        data.put("name", "李逍遙");
        baos = PdfUtil.createPDF(data, "zhengshu.ftl");
        String fileName = "獲獎證書.pdf";
        File file = new File(fileName);
        out = new FileOutputStream(file);
        baos.writeTo(out);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if(baos!=null){
            baos.close();
        }
        if(out != null){
            out.close();
        }
    }
}

使用controller

mport java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/pdf")
public class PdfController {
	
	@RequestMapping("/export")
	public void exportPdf(HttpServletResponse response) throws Exception{
		ByteArrayOutputStream baos = null;
		OutputStream out = null;
		try {
			// 模板中的資料,實際運用從資料庫中查詢
			Map<String,Object> data = new HashMap<>();
			data.put("name", "李逍遙");
			baos = PdfUtil.createPDF(data, "zhengshu.ftl");;
			// 設定響應訊息頭,告訴瀏覽器當前響應是一個下載檔案
			response.setContentType( "application/x-msdownload");
			// 告訴瀏覽器,當前響應資料要求使用者干預儲存到檔案中,以及檔名是什麼 如果檔名有中文,必須URL編碼 
			String fileName = URLEncoder.encode("獲獎證書.pdf", "UTF-8");
			response.setHeader( "Content-Disposition", "attachment;filename=" + fileName);
			out = response.getOutputStream();
			baos.writeTo(out);
			baos.close();
		} catch (Exception e) {
			e.printStackTrace();
		    throw new Exception("匯出失敗:" + e.getMessage());
		} finally{
			if(baos != null){
				baos.close();
			}
			if(out != null){
				out.close();
			}
		}
	}
}

4. 橫向列印

有時網頁比較寬時,生成的pdf寬度不夠,導致顯示內容不完整,可以通過在模板css設定@page控制

/*設定頁面寬高 A4大小*/
@page{size:297mm 210mm;}

參考: