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>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{
             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;
             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{
             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;
             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;}

参考:

原文地址:https://www.cnblogs.com/linyufeng/p/13402901.html