使用HTML或者FTL(Freemarker模板)生成PDF

方法一:用 Flying-Saucer 生产pdf

项目依赖

        <dependency>
			<groupId>org.freemarker</groupId>
			<artifactId>freemarker</artifactId>
			<version>2.3.28</version>
		</dependency>


		<dependency>
			<groupId>org.xhtmlrenderer</groupId>
			<artifactId>flying-saucer-pdf</artifactId>
			<version>9.0.8</version>
		</dependency>

项目中建立目录 template/font   字体文件(arialuni.ttf、simsun.ttc)和图片(logo.png)放在 font 目录下,模板文件

protocolTemplate.ftl 放在template目录下。

主要的操作类PdfUtils

package com.ssish.eoms.util;


import com.lowagie.text.DocumentException;
import freemarker.core.ParseException;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
import org.xhtmlrenderer.pdf.ITextRenderer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>Title: PdfUtils.java</p>
 * <p>Description: PDF生成工具类</p>
 * <p>Company:  </p>
 * <p> Just go on !!!</p>
 * @date 2018年3月1日 下午8:44:18 
 * @version v1.0
 */
@SuppressWarnings("all")
public class PDFUtil {
	
	static BASE64Encoder encoder = new BASE64Encoder();
    static BASE64Decoder decoder = new BASE64Decoder();
    
	/**
	 * <p>Description: 生成PDF到文件</p>
	 * <p>Company: </p>
	 * 
	 * @param ftlPath  模板文件路径(不含文件名)
	 * @param ftlName  模板文件(不含路径)
	 * @param imageDiskPath 图片的磁盘路径
	 * @param data 数据 (填到模板上的数据)
	 * @param outputFile 目标文件(全路径名称)
	 * @throws Exception
	 */
	public static void generateToFile(String ftlPath, String ftlName, String imageDiskPath, Object data,String outputFile) throws Exception {
	        OutputStream out = null;
		ITextRenderer render = null;
		try {
			String html = PdfHelper.getPdfContent(ftlPath, ftlName, data);	//组装模板和数据生成html串
			out = new FileOutputStream(outputFile);
			render = PdfHelper.getRender();
			render.setDocumentFromString(html);		//此处抛异常
			if (imageDiskPath != null && !imageDiskPath.equals("")) {
				// html中如果有图片,图片的路径则使用这里设置的路径的相对路径,这个是作为根路径
				render.getSharedContext().setBaseURL("file:/" + imageDiskPath);
			}
			render.layout();
			render.createPDF(out);
			render.finishPDF();
			render = null;
		} catch (Exception e) {
			System.out.println("Exception:"+e);
			throw e;
		} finally{
			if (out != null) {
		        try {
					out.close();
				} catch (IOException e) {
					System.out.println("Exception:"+e);
					throw e;
				}
			}
		}
	}
 
 
	/**
	 * 生成PDF到输出流中(ServletOutputStream用于下载PDF)
	 * 
	 * @param ftlPath
	 *            ftl模板文件的路径(不含文件名)
	 * @param ftlName
	 *            ftl模板文件的名称(不含路径)
	 * @param imageDiskPath
	 *            如果PDF中要求图片,那么需要传入图片所在位置的磁盘路径
	 * @param data
	 *            输入到FTL中的数据
	 * @param response
	 *            HttpServletResponse
	 * @return
	 * @throws TemplateNotFoundException
	 * @throws MalformedTemplateNameException
	 * @throws ParseException
	 * @throws IOException
	 * @throws TemplateException
	 * @throws DocumentException
	 */
	public static OutputStream generateToServletOutputStream(String ftlPath, String ftlName, String imageDiskPath,
			Object data, HttpServletResponse response) throws Exception {
		String html = PdfHelper.getPdfContent(ftlPath, ftlName, data);
		OutputStream out = null;
		ITextRenderer render = null;
		out = response.getOutputStream();
		render = PdfHelper.getRender();
		render.setDocumentFromString(html);
		if (imageDiskPath != null && !imageDiskPath.equals("")) {
			// html中如果有图片,图片的路径则使用这里设置的路径的相对路径,这个是作为根路径
			render.getSharedContext().setBaseURL("file:/" + imageDiskPath);
		}
		render.layout();
		render.createPDF(out);
		render.finishPDF();
		render = null;
		return out;
	}


	/**
     *  将PDF转换成base64编码
     *  1.使用BufferedInputStream和FileInputStream从File指定的文件中读取内容;
     *  2.然后建立写入到ByteArrayOutputStream底层输出流对象的缓冲输出流BufferedOutputStream
     *  3.底层输出流转换成字节数组,然后由BASE64Encoder的对象对流进行编码
     * */
    public static String getPDFBinary(File file) {
    	FileInputStream fin =null;
    	BufferedInputStream bin =null;
    	ByteArrayOutputStream baos = null;
    	BufferedOutputStream bout =null;
    	try {
    		//建立读取文件的文件输出流
    		fin = new FileInputStream(file);
    		//在文件输出流上安装节点流(更大效率读取)
    		bin = new BufferedInputStream(fin);
    		// 创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量
    		baos = new ByteArrayOutputStream();
    		//创建一个新的缓冲输出流,以将数据写入指定的底层输出流
    		bout = new BufferedOutputStream(baos);
    		byte[] buffer = new byte[1024];
    		int len = bin.read(buffer);
    		while(len != -1){
    			bout.write(buffer, 0, len);
    			len = bin.read(buffer);
    		}
    		//刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
    		bout.flush();
    		 byte[] bytes = baos.toByteArray();
    		 //sun公司的API
//    		 return encoder.encodeBuffer(bytes).trim();  
    		 //apache公司的API
    		 //return Base64.encodeBase64String(bytes);
    		
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				fin.close();
				bin.close();
				//关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException
				//baos.close();
				bout.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
    	return null;
    }
        
    /**
     * 将base64编码转换成PDF
     * @param base64sString
     *  1.使用BASE64Decoder对编码的字符串解码成字节数组
     *  2.使用底层输入流ByteArrayInputStream对象从字节数组中获取数据;
     *  3.建立从底层输入流中读取数据的BufferedInputStream缓冲输出流对象;
     *  4.使用BufferedOutputStream和FileOutputSteam输出数据到指定的文件中
     */
    static void base64StringToPDF(String base64sString) {
    	BufferedInputStream bin = null;
    	FileOutputStream fout = null;
    	BufferedOutputStream bout = null;
    	try {
    		 //将base64编码的字符串解码成字节数组
			byte[] bytes = decoder.decodeBuffer(base64sString);
    		//apache公司的API
    		//byte[] bytes = Base64.decodeBase64(base64sString);
			//创建一个将bytes作为其缓冲区的ByteArrayInputStream对象
			ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
			//创建从底层输入流中读取数据的缓冲输入流对象
			bin = new BufferedInputStream(bais);
			//指定输出的文件
			File file = new File("/home/amarsoft/test.pdf");
			//创建到指定文件的输出流
			fout  = new FileOutputStream(file);
			//为文件输出流对接缓冲输出流对象
			bout = new BufferedOutputStream(fout);
			
			byte[] buffers = new byte[1024];
			int len = bin.read(buffers);
			while(len != -1){
				bout.write(buffers, 0, len);
				len = bin.read(buffers);
			}
			//刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
			bout.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				bin.close();
				fout.close();
				bout.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
    }

	public  static  void  main(String[] args) {
		try  {

			Map<Object, Object> o= new HashMap<Object, Object>();
			o.put( "name" ,  "张三" );

			String path=PdfHelper.getPath();
			System.out.println("path="+path);

			generateToFile(path,  "protocolTemplate.ftl" , path+"font/" , o,  "D:\\demo.pdf" );

			/*如果需要PDF的下载,可以通过generateToServletOutputStream这个方法来获取PDF的输出流,然后通过response写到客户端去*/
		}  catch  (Exception e) {
			e.printStackTrace();
		}

	}

}

辅助类PdfHelper

package com.ssish.eoms.util;

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.core.ParseException;
import freemarker.template.*;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.util.Locale;

/**
 * @author wyw
 * @Description TODO
 * @date 2019/7/16 18:33
 */
public class PdfHelper {

    public  static ITextRenderer getRender() throws DocumentException, IOException, URISyntaxException {

        ITextRenderer render =  new  ITextRenderer();

        String path = getResourcesFilePath("template/font/");
        //添加字体,以支持中文
        render.getFontResolver().addFont(path +  "arialuni.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        render.getFontResolver().addFont(path +  "simsun.ttc" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        return  render;
    }

    //获取要写入PDF的内容
    public  static  String getPdfContent(String ftlPath, String ftlName, Object o)  throws TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {
        return  useTemplate(ftlPath, ftlName, o);
    }

    //使用freemarker得到html内容
    public  static  String useTemplate(String ftlPath, String ftlName, Object o)  throws  TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, TemplateException {

        String html =  null ;

        Template tpl = getFreemarkerConfig(ftlPath).getTemplate(ftlName);
        tpl.setEncoding( "UTF-8" );

        StringWriter writer =  new  StringWriter();
        tpl.process(o, writer);
        writer.flush();
        html = writer.toString();
        return  html;
    }

    /**
     * 获取Freemarker配置
     * @param templatePath
     * @return
     * @throws IOException
     */
    private  static Configuration getFreemarkerConfig(String templatePath)  throws  IOException {
        Configuration config =  new  Configuration();
        config.setDirectoryForTemplateLoading( new File(templatePath));
        config.setEncoding(Locale.CHINA,  "utf-8" );
        return  config;
    }

    /**
     * 获取类路径
     * @return
     */
    public  static  String getPath(){
        //return PDFUtil.class.getResource("").getPath().substring(1);	//返回类路径(当前类所在的路径)
        return PDFUtil.class.getResource("/").getPath();	//返回项目根路径(编译之后的根路径)
    }

    /**
     * 通过文件名称获取resources资源文件夹下文件绝对路径
     * @param fileName
     * @return 模块文件完整路径
     * @throws URISyntaxException
     */
    public static String getResourcesFilePath(String fileName) throws URISyntaxException {
        StringBuilder builder = new StringBuilder();
        //判断是否Linux系统
        if (System.getProperty("os.name").toLowerCase().indexOf("linux") != -1) {
            //linux系统在路径前面加/
            builder.append("/");
        }
        //获取项目路径this.getClass()
        String srcPath = PDFUtil.class.getResource("/").toURI().getPath();
        System.out.println(srcPath);
        if (srcPath.contains("WEB-INF")) {
            builder.append(srcPath.substring(1, srcPath.lastIndexOf("WEB-INF")));
        } else {
            builder.append(srcPath.substring(1));
        }
        //builder.append("resources/");
        builder.append(fileName);
        return builder.toString();
    }
}
模板文件protocolTemplate.ftl
<html>
<head>
    <meta charset="UTF-8"></meta>
    <title>
        服务协议
    </title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"></meta>

    <style type="text/css">
@page {
 size:A4;
}
html,
body {
    background-color: #fff;

}

* {
    padding: 0;
    margin: 0;
}
img {
    display: block;
}
.relativeAgreement {
    padding: 10px 15px 30px;
}
.relativeAgreement p {
    color: #333;
}
.perLineTit1 {
    margin-bottom: 20px;
}
.perLineTit {
    font-size: 15px;
    font-weight: bold;
    color: #444;
    text-align: center;
}
.normal {
    font-size: 15px;
    color: #444;
}
.indent2em {
    font-size: 15px;
    text-indent: 2em;
}
.logoImg {
    width: 200px;
    margin: 10px auto 12px;
}
.marginTop30 {
    margin-top: 20px;
}
.textAlignRight {
    text-align: right;
    font-size: 15px;
}
p.normalTit {
    margin-bottom: 15px;
}
.marginbot {
    margin-bottom: 140px;
}
</style>
</head>
<body style="font-family: SimSun;">
<div class="main">
    <div class="relativeAgreement">
        <img src="logo.png" alt="" class="logoImg" />
        <p class="perLineTit">《保险经纪服务委托协议书》</p>
        <p class="perLineTit perLineTit1">授权委托书</p>
        <p class="normal normalTit">
            致:有关保险公司
        </p>
        <p class="indent2em">
            从${(data.year)!}年${(data.month)!}月${(data.day)!}日起,我司委托宇泰保险经纪(北京)有限公司为我司唯一的保险经纪人,代表我们处理所有保险相关事宜。
        </p>
        <p class="indent2em">
            除非另行下达了取消该委托书的书面通知,本委托书将持续有效。同时,所有以前有关这方面的委托全部作废。
        </p>
        <p class="indent2em">
            宇泰保险经纪(北京)有限公司被授权审核我司的保险计划及安排,并按照其营业执照所规定的营业范围提供风险管理及保险服务。
        </p>
        <p class="normal">
            主要服务范围如下:
        </p>
        <p class="normal">①协助我司安排相关保险;</p>
        <p class="normal">②帮助我司识别未投保风险;</p>
        <p class="normal">③代我司签署保险协议;</p>
        <p class="normal">
            ④帮助我司检查并核对各种保险文件及其他有关风险转移安排的文件,包括保险单和批单;
        </p>
        <p class="normal">⑤准备保险安排概要;</p>
        <p class="normal">
            就风险/保险及赔偿等问题向我司提供建议、帮助及指导;
        </p>
        <p class="normal">⑥与我司举行保险工作会议;</p>
        <p class="normal">⑦检测保险公司的财务稳定性;</p>
        <p class="normal">⑧协助我司进行索赔,帮助并指导我司准备索赔文件。</p>
        <p class="normal marginTop30">
            委托人:(盖章)
            <img alt="盖章"  style="vertical-align:middle;margin-left:50px;" src="${(data.imageUrl)!}" />
        </p>
        <p class="textAlignRight" >
            ${(data.year)!}年${(data.month)!}月${(data.day)!}日
        </p>
    </div>


    <!-- 标记分页
        <div style="page-break-after:always; border:1px solid blue;"></div> -->
</div>
</body>
</html>

主要调用代码

@RequestMapping(value = Urls.SSI_BACK_PROTOCOL_MAKE_SEAL, method = RequestMethod.POST)
    public void makeSeal(HttpServletResponse response,  String  imgData,String procotolId) throws IOException {
        JSONObject jb = new JSONObject();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String filePathAdd ="protocol/img/"+sdf.format(new Date())+".png";
        System.out.println("filePathAdd="+filePathAdd);
        ossService.upload(new ByteArrayInputStream(Base64.decode(imgData)), filePathAdd);

        try {
            String pdfurl = createPDF2(response,procotolId,filePathAdd);
            System.out.println("pdfurl="+pdfurl);
            jb.put("retFlag", "true");
            jb.put("pdfurl", pdfurl);
        } catch (TemplateException e) {
            jb.put("retFlag", "flase");
            jb.put("retMsg", e.getMessage());
        }
        writeDateToWeb(response, jb);
    }
public String createPDF2(HttpServletResponse response,  String  procotolId,String filePath) throws IOException, TemplateException {
        log.info("==========================进入生成服务协议pdf");
        if(StringUtils.isNotBlank(procotolId)){
            WxServiceAgreement wxServiceAgreement =  protocolService.queryProtocolById(procotolId);
            if(wxServiceAgreement!=null){
                //拼接签名储存路径
                StringBuilder builder = new StringBuilder();

                ProtocolVo protocolVo = new ProtocolVo();
                protocolVo.setName(wxServiceAgreement.getClientName());
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                String cerateStr = sdf.format(wxServiceAgreement.getCreateDate());
                protocolVo.setYear(cerateStr.substring(0,4));
                protocolVo.setMonth(cerateStr.substring(5,7));
                protocolVo.setDay(cerateStr.substring(8,10));
                String imageUrl = newbeeFileWeb+"/"+newbeeObjectName+filePath;
                System.out.println("imageUrl="+imageUrl);
                protocolVo.setImageUrl(imageUrl);
                try  {

                    Map<Object, Object> o= new HashMap<Object, Object>();
                    o.put( "data" ,  protocolVo );

                    //String path=PdfHelper.getPath();
                    //System.out.println("path="+path);
                    String fontPath = PdfHelper.getResourcesFilePath("template/");
                    System.out.println("FontPath===================" + fontPath);



                    String exportFilePath = "/data/eoms/temp/";
                    //判断文件储存目录是否存在
                    File saveDir = new File(exportFilePath);
                    if (!saveDir.exists()) {
                        saveDir.mkdirs();
                        log.info("============create directory:" + exportFilePath);
                    }

                    exportFilePath = exportFilePath+ wxServiceAgreement.getServiceNo()+".pdf";

                    PDFUtil.generateToFile(fontPath,  "protocolTemplate.ftl" , fontPath+"font/" , o,  exportFilePath );
                    /*如果需要PDF的下载,可以通过generateToServletOutputStream这个方法来获取PDF的输出流,然后通过response写到客户端去*/

                    //上传到oss
                    File pdfFile = new File(exportFilePath);
                    InputStream isZipFile = new FileInputStream(pdfFile);
                    String osspath="protocol/pdf/"+pdfFile.getName();
                    boolean flag = ossService.upload(isZipFile,osspath);
                    log.info("===============flag="+flag);
                    log.info("===============osspath="+osspath);

                    WxServiceAgreement serviceAgreement = new WxServiceAgreement();
                    serviceAgreement.setId(wxServiceAgreement.getId());
                    serviceAgreement.setFileUrl(newbeeFileWeb+"/"+newbeeObjectName+osspath);
                    protocolService.updatebyKey(serviceAgreement);

                    pdfFile.delete();

                    return newbeeFileWeb+"/"+newbeeObjectName+osspath;

                }  catch  (Exception e) {
                    e.printStackTrace();
                }



            }
        }
        return  null;
    }

生成后的pdf

注意事项:

1.中文支持问题

因为这个框架是外国人写的,所有的flying-saucer jar包中均不支持中文,但是外国人提供了一个字体类:BaseFont类。只要在这个类中,加入你想要支持的字体文件即可(.afm,.pfm,.ttf ,.otf ,.ttc 均支持),具体代码如下

String path = getResourcesFilePath("template/font/"); //此处得到的路径是项目中 template/font/ 编译后所在的路径
//添加字体,以支持中文
render.getFontResolver().addFont(path +  "arialuni.ttf" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
render.getFontResolver().addFont(path +  "simsun.ttc" , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

2.在你的模板文件上加上字体!html上加上字体!html上加上字体!重要的事情说三遍!!! 否则,pdf中中文不显示(只能加项目中已经引入了的字体)

<body style="font-family: SimSun;">

3.换行问题 flying-saucer-pdf这个jar包在9.0.1版本之前都是不支持中文换行的 ,强烈不推荐使用9.0.1及其之前的版本!!!

4.路径问题

要导出的html中有图片,图片的路径

图片的路径必须是绝对路径才可以识别哦~代码如下:


render.getSharedContext().setBaseURL("file://"+Thread.currentThread().getContextClassLoader().getResource(imagePath).getPath());
 
//imagePath 为:图片在html中使用的路径
//这里路径eg : file:///E:...的路径,在windows和linux中均可以识别。


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值