freemarker + ItextRender 根据模板生成PDF文件

1. 制作模板

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml">
<head lang="en">
  <meta http-equiv="content-type" content="text/html;charset=utf-8"></meta>
  <title>软件包隐患扫描报告</title>
  <style>
    .template_ftl {
      width: 100%;
      height: 100%;
      padding: 30px 25px;
    }

    .template_box {
      width: 600px;
      height: 842px;
      box-sizing: border-box;
      margin: 0 auto;
    }

    .template_ftl .template_header {

      width: 100%;
      height: 50px;
      box-sizing: border-box;
      color: #0D122B;
    }

    .template_ftl .template_header > i {
      display: inline-block;
      width: 24px;
      height: 28px;
      background: url("stack.png") no-repeat center center;
    }

    .template_ftl .template_header > h2 {
      display: inline-block;
      font-size: 17px;
    }

    .template_ftl .template_header > div {
      float: right;
      color: #B7C1CF;
      font-size: 9px;
      line-height: 50px;
    }

    .template_ftl .template_header > div > i {
      display: inline-block;
      width: 9px;
      height: 9px;
      background: url("Calendar.png") no-repeat center center;
    }

    .template_ftl .template_description {
      width: 100%;
      margin-top: 10px;
    }

    .template_ftl .template_title {
      width: 100%;
      height: 18px;
      line-height: 18px;
      color: #00AEA1;
      font-size: 12px;
    }

    .template_ftl .template_title > i {
      display: inline-block;
      width: 14px;
      height: 14px;
      margin-right: 10px;
      background: url("Layers.png") no-repeat center center;
    }

    .template_ftl .template_info {
      list-style: none;
      padding-left: 30px;
    }

    .template_ftl .template_info label {
      display: inline-block;
      width: 80px;
      margin-right: 30px;
      color: #646464;
      font-size: 10px;
      vertical-align: middle;
    }

    .template_ftl .template_info span {
      color: #0D122B;
      font-size: 10px;
      vertical-align: middle;
    }

    .template_ftl .template_info li {
      margin-bottom: 5px;
    }

    .template_ftl .template_title .shield {
      width: 14px;
      height: 14px;
      background: url("Shield.png") no-repeat center center;
    }

    .template_ftl .template_title .screen {
      width: 14px;
      height: 14px;
      background: url("Screen.png") no-repeat center center;
    }

    .template_ftl .template_network {
      list-style: none;
      padding-left: 30px;
      margin-top: 10px;
    }

    .template_ftl .template_network .network_level b {
      color: #0D122B;
      font-size: 12px;
    }

    .template_ftl .template_network .network_level span {
      color: #E74C3C;
      font-size: 12px;
    }

    .template_ftl .template_network .network_level p {
      margin: 0;
      color: #646464;
      font-size: 10px;
    }

    .template_ftl .template_network .network_content:before {
      content: '● ';
      color: #00AEA1;
    }

    .template_ftl .template_network .network_content {
      color: #3E4550;
      font-size: 10px;
      margin-top: 10px;
    }

    .online_info {
      width: 100%;
    }

    .online_info .online_list {
      width: 100%;
     
      box-sizing: border-box;
      list-style: none;
      padding-left: 30px;
      display: flex;
      flex-flow: row nowrap;
      justify-content: space-between;
      align-items: center;
      margin: 0;
      color:#3E4550;
      font-size: 10px;
    }
    .online_info .online_list:nth-child(2n){
      background-color: #EFEFEF;
    }
    .online_info .online_list > td {
      width: 90px;
      line-height: 25px;
      text-align: center;
     
    }
    .online_info .online_list > td:first-child {
      text-align: center;
    }
     
  </style>
</head>
<body>
<div class="template_ftl" style="font-family:SimSun;">

  <div class="template_box">
    <div class="template_header">
      <i></i>

      <span style="font-size:24px;font-weight:1px bold;">软件包扫描报告</span>

      <div>
        <i></i>
        <span>
        	<#if (currentDate)??>
        		${currentDate}
        	</#if>
        </span>
      </div>
    </div>
    <div class="template_description">
      <div class="template_title">
        <i></i>
        <span>扫描概要信息</span>
      </div>
      <ul class="template_info">
        <li>
          <label>扫描产品名称</label>
          <span>
          	<#if (softScanInfo.productName)??>
				${softScanInfo.productName}
			</#if>
		 </span>
        </li>
        <li>
          <label>文件路径</label>
          <span>
		  		<#if (softScanInfo.scanFilePath)??>
					${softScanInfo.scanFilePath}
				</#if>
		  </span>
        </li>
        <li>
          <label>扫描开始时间</label>
          <span>
          	<#if (softScanInfo.scanBegintime)??>
				${(softScanInfo.scanBegintime)?string("yyyy年MM月dd日 HH:mm:ss")}
			</#if>
          </span>
        </li>
        <li>
          <label>扫描结束时间</label>
          <span>
          	<#if (softScanInfo.scanEndtime)??>
				${(softScanInfo.scanEndtime)?string("yyyy年MM月dd日 HH:mm:ss")}
			</#if>
          </span>
        </li>
      </ul>
    </div>
    <div class="template_description">
      <div class="template_title">
        <i class="shield"></i>
        <span>软件包安全信息</span>
      </div>
      <ul class="template_network">
        <li class="network_level">
          <span style="font-size:13px;color:black;">软件包风险等级为 </span>
		  
		  	<#if (softScanInfo.scanResult)??>
				<#if (softScanInfo.scanResult == '高')>
					<span style="color:red;">${softScanInfo.scanResult}</span>
					<#elseif (softScanInfo.scanResult == '中')>
						<span style="color:#FF9900;">${softScanInfo.scanResult}</span>
					<#elseif (softScanInfo.scanResult == '低')>
						<span style="color:#0000FF;">${softScanInfo.scanResult}</span>
					<#else> 
						<span style="color:#00cc00;">安全</span>
				</#if>
			<#else>
				<span style="color:#00cc00;">安全</span>
		    </#if>
          <p style="margin-top:8px;">本次共扫描1个软件包,其中包含<#if recordList?exists>${recordList?size} <#else>0</#if>款软件</p>
        </li>
		<li class="network_content">
          <span>
          		风险等级为<span style="color:red;">高</span>的软件数为
				<span style="color:red;"><#if (highRiskRecord.appScanResult)??>${highRiskRecord.appScanResult}</span></#if>
		  </span>
        </li>
		<li class="network_content">
          <span>
          		风险等级为<span style="color:#FF9900;">中</span>的软件数为
				<span style="color:#FF9900;"><#if (middleRiskRecord.appScanResult)??>${middleRiskRecord.appScanResult}</span></#if>
		  </span>
        </li>
		<li class="network_content">
          <span>
          		风险等级为<span style="color:#0000FF;">低</span>的软件数为
				<span style="color:#0000FF;"><#if (lowRiskRecord.appScanResult)??>${lowRiskRecord.appScanResult}</span></#if>
		  </span>
        </li>
		<li class="network_content">
          <span>
          		风险等级为<span style="color:#00cc00;">安全</span>的软件数为
				 <span style="color:#00cc00;"><#if (noneRiskRecord.appScanResult)??>${noneRiskRecord.appScanResult}</span></#if>
		  </span>
        </li>
      </ul>
    </div>
    <div class="template_description">
      <div class="template_title">
        <i class="screen"></i>
        <span>扫描软件包概要信息</span>
      </div>
      <div class="online_info" >
	     <table class="online_list" style="word-break: break-all; word-wrap: break-word;">
	        <tr class="online_list" style="border-bottom: 1px solid #B7C1CF;color: #94949;">
	          <td >软件名称</td>
	          <td style="width:120px;">版本号</td>
	          <td style="width:150px;">软件包名称</td>
	          <td style="width:40px;">安全风险</td>
	          <td style="width:150px;">安全描述事件</td>
	          <td style="width:30px;">通过</td>
	        </tr>
	        <#if recordList?exists>
				<#list recordList as record>
					<tr class="online_list" >
						<td>
							<#if (record.appName)??>
								${record.appName}
							</#if>
						</td>
				        <td style="width:120px;">
				        	<#if (record.appVersion)??>
								${record.appVersion}
							</#if>
				        </td>
				        <td style="width:150px;">
				        	<#if (record.appFileName)??>
								${record.appFileName}
							</#if>
				        </td>
				        <td style="width:30px;">
				        	<#if (record.riskRating)??>
				        		<#if (record.riskRating == '无')>
				        			安全
				        		<#else>
									${record.riskRating}
								</#if>
							<#else>
								安全
							</#if>
				        </td>
				        <td style="width:150px;">
							<#if (record.riskDescription)??>
								${record.riskDescription}
							<#else>
								无
							</#if>
						</td>
				        <td style="width:20px;">
				        	<#if (record.appScanResult)??>
				        		<#if (record.appScanResult = 1)>
						           <i style="color: #00AEA1">√</i>
				        		<#else>
				        			<i style="color: #E74C3C">×</i>
				        		</#if>
							<#else>
								<i style="color: #00AEA1">√</i>
							</#if>
				        </td>
					</tr>
				</#list>
			</#if>
		</table>
      </div>
    </div>
  </div>
</div>
</body>
</html>

2. 获取模板,并将所获取的数据加载生成html文件

public String getHtmlString (final Object data, final String templateDirectory, final String templateName, final String encoding) {
	log.info("FreeMarkerUtil!");
	String html = null;
	StringWriter stringWriter = null;
	BufferedWriter writer = null;
	try {
		//创建一个负责管理FreeMarker模板的Configuration实例
		final Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
		//指定FreeMarker模板文件位置
//		configuration.setDirectoryForTemplateLoading(new File(templateDirectory));
			
		//设置模板的编码格式
		configuration.setEncoding(Locale.CHINA, encoding);
		//获取模板
		configuration.setClassForTemplateLoading(this.getClass(), "/templates");
		final Template template = configuration.getTemplate(templateName, encoding);
		stringWriter = new StringWriter();
		writer = new BufferedWriter(stringWriter);
		//将数据输出到html中
		template.process(data, writer);
		writer.flush();
		html = stringWriter.toString();
	} catch (Exception e) {
		log.error("failed to get html String!", e);
	} finally {
		if (stringWriter !=null) {
			try {
				stringWriter.close();
			} catch (IOException e) {
				log.error("failed to close writer!", e);
			}
		}
		if (writer !=null) {
			try {
				writer.close();
			} catch (IOException e) {
				log.error("failed to close writer!", e);
			}
		}
	}
	log.info("getHtmlString() exit!");
	return html;
}

2. 生成PDF文件

final File file = new File(reportPath);
try {
     if (!file.exists()) {
          file.createNewFile();
     }
     final String chineseFont = getChineseFont();//获取字体文件路径
     log.info("chineseFont=" + chineseFont);
     createPDF(new FileOutputStream(file), chineseFont, html);
     
} 
public void createPDF(final OutputStream outputStream, final String fontPath, final String html) {
        final ITextRenderer renderer = new ITextRenderer();
        // 设置字体样式
        try {
            renderer.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            //这里的字体必须和ftl模板中的字体一致,否则中文会丢失
            // 把html代码传入渲染器中
            renderer.setDocumentFromString(html);
            /*
             * 解决图片的相对路径问题,必须在设置document后再设置图片路径,不然不起作用,
             * 如果使用绝对路径依然有问题,可以在路径前面加"file:/"
             */
            // renderer.getSharedContext().setBaseURL("file:/C:/Users/HYH/Desktop/templateftp/");
            renderer.getSharedContext().setBaseURL("file:" + getPictureDirctory());
            log.info("images directory path: file:" + getPictureDirctory());

            renderer.layout();
            renderer.createPDF(outputStream, false);
            renderer.finishPDF();
            outputStream.flush();
        } catch (final Exception e) {
            log.error("failed to create pdf!", e);
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (final IOException e) {
                    log.error("failed to close outputStream!", e);
                }
            }
        }
        log.info("create pdf completely!");
    }

其中由两个地方需要注意,都是关于获取文件路径的问题,由于项目部署的时候是打包成jar包形式,所以在开发过程中时直接安照传统的获取方法没有一点文件,但是当打包后部署,总是出错。于是参考网上文章,先将文件读出来到项目的临时目录下,然后再按正常方式加载该临时文件;

//获取图片目录位置
    private String getPictureDirctory () {
    	final File fileDir = new File("System.getProperty("user.dir") + "/images/"");
    	if (!fileDir.exists()) {
    		fileDir.mkdirs();
    		final InputStream calendarStream = getClass().getClassLoader().getResourceAsStream("images/Calendar.png");
    		final InputStream deleteStream = getClass().getClassLoader().getResourceAsStream("images/delete.png");
    		final InputStream layersStream = getClass().getClassLoader().getResourceAsStream("images/Layers.png");
    		final InputStream saveStream = getClass().getClassLoader().getResourceAsStream("images/save.png");
    		final InputStream screenStream = getClass().getClassLoader().getResourceAsStream("images/Screen.png");
    		final InputStream shieldStream = getClass().getClassLoader().getResourceAsStream("images/Shield.png");
    		final InputStream stackStream = getClass().getClassLoader().getResourceAsStream("images/stack.png");
    		
    		final File calendarFile = new File(IMAGES_TEMP_PATH + "Calendar.png");
    		final File deleteFile = new File(IMAGES_TEMP_PATH + "delete.png");
    		final File layersFile = new File(IMAGES_TEMP_PATH + "Layers.png");
    		final File saveFile = new File(IMAGES_TEMP_PATH + "save.png");
    		final File screenFile = new File(IMAGES_TEMP_PATH + "Screen.png");
    		final File shieldFile = new File(IMAGES_TEMP_PATH + "Shield.png");
    		final File stackFile = new File(IMAGES_TEMP_PATH + "stack.png");
    		
    		try {
				FileUtils.copyInputStreamToFile(calendarStream, calendarFile);
				FileUtils.copyInputStreamToFile(deleteStream, deleteFile);
				FileUtils.copyInputStreamToFile(layersStream, layersFile);
				FileUtils.copyInputStreamToFile(saveStream, saveFile);
				FileUtils.copyInputStreamToFile(screenStream, screenFile);
				FileUtils.copyInputStreamToFile(shieldStream, shieldFile);
				FileUtils.copyInputStreamToFile(stackStream, stackFile);
			} catch (IOException e) {
				log.error("图片文件复制失败!", e);
			}
    	}
    	return IMAGES_TEMP_PATH;
    }
/**
     * 获取中文字体位置
     * @return
     */
    private String getChineseFont() {

        final InputStream stream = getClass().getClassLoader().getResourceAsStream("fonts/simsun.ttf");
        final File fileDir = new File("System.getProperty("user.dir") + "/fonts/"");
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        final String fontsPath = FONTS_TEMP_PATH + "simsun.ttf";
        final File targetFile = new File(fontsPath);
        try {
            FileUtils.copyInputStreamToFile(stream, targetFile);
        } catch (final IOException e) {
            log.error("字体文件复制失败!", e);
        }
        return fontsPath;
    }

还有一个问题至今没有解决,就是关于生成PDF文件后自动换行的问题,参考了网上大多数的作法,都是修改模板样式,生成的html确实是可以换行,但是对PDF无效。最后想出了一个比较笨也不是很合适的方法,在后台先给它在某处先换好行,然后再生成PDF,虽然比较笨,也存在问题,但是确实解了燃眉之急。

/**
     * 向字符串source中每隔sep个插入一个replace
     * @param source
     * @param sep
     * @param replace
     * @return
     */
    public String changeLine(final String source, final int sep, final String replace) {
    	if (source == null) {
			return null;
		}
    	final StringBuffer buffer = new StringBuffer(source);
		for (int i = sep; i < source.length(); i+=(sep + 1)) {
			buffer.insert(i, replace);
		}
		return buffer.toString();
	}

最终结果


如果有大神知道如何正确实现自动换行,还请不吝赐教。

猜你喜欢

转载自blog.csdn.net/u012843873/article/details/80581108