freemarker生成PDF文件

環(huán)境準(zhǔn)備

開(kāi)發(fā)環(huán)境

java8,SpringBoot 2.1.4字符集GBK

字體

宋體--simsun.ttf

pom依賴(lài)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>kernel</artifactId>
    <version>7.0.3</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>io</artifactId>
    <version>7.0.3</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>forms</artifactId>
    <version>7.0.3</version>
</dependency>
<!-- 解決中文字體問(wèn)題 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>font-asian</artifactId>
    <version>7.0.3</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>
<dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.13</version>
</dependency>

模板生成PDF

將模板轉(zhuǎn)換為html

從文件讀取模板

/**
  * 初始化freemarker配置
  * templateRoot:模板文件根目錄
  */
Configuration freemarkerCfg = initFreemarkerCfg(templateRoot);
/**
  * 將模板轉(zhuǎn)換為HTML字符串
  */
String content = freeMarkerRender(data, freemarkerCfg, htmlTemplate, charSet);
private static Configuration initFreemarkerCfg(String templateRoot) {
   Configuration freemarkerCfg = new Configuration();
   try {
      freemarkerCfg.setDirectoryForTemplateLoading(new File(templateRoot));
   } catch (IOException e) {
      log.error("模板根路徑獲取失敗!" + templateRoot, e);
      throw new RuntimeException("模板根路徑獲取失敗!" + templateRoot,e);
   }
   return freemarkerCfg;
}
/**
  * data 需要注入模板的數(shù)據(jù)
  * freemarkerCfg freemarker配置
  * htmlTmp 模板名稱(chēng)
  * charSet 字符集 linux下使用UTF-8,windows下使用GBK,否則會(huì)出現(xiàn)中文亂碼,模板文件的文件編碼和聲明編碼同樣需要保持一致
  */
private static String freeMarkerRender(Map<String, Object> data, Configuration freemarkerCfg, String htmlTmp,String charSet) {
   try (Writer out = new StringWriter();) {
      Template template = freemarkerCfg.getTemplate(htmlTmp);
      template.setEncoding(charSet);
      template.process(data, out);
      out.flush();
      return out.toString();
   } catch (Exception e) {
      log.error("HTML加載數(shù)據(jù)失敗!", e);
      throw new RuntimeException("HTML加載數(shù)據(jù)失敗!", e);
   }
}

從流讀取模板

/**
  * data 需要注入模板的數(shù)據(jù)
  * fileName 文件名稱(chēng)
  * inputStream 模板文件流
  * charSet 字符集 linux下使用UTF-8,windows下使用GBK,否則會(huì)出現(xiàn)中文亂碼,模板文件的文件編碼和聲明編碼同樣需要保持一致
  */
private static String freeMarkerRender(Map<String, Object> data, String fileName, InputStream inputStream,
      String charSet) {
   try (Writer out = new StringWriter();
         InputStreamReader inputStreamReader = new InputStreamReader(inputStream);) {
      Configuration configuration = new Configuration();
      Template template = new Template(fileName, inputStreamReader, configuration);
      template.setEncoding(charSet);
      template.process(data, out);
      out.flush();
      return out.toString();
   } catch (Exception e) {
      log.debug("HTML加載數(shù)據(jù)失敗!", e);
      throw new RuleException(ErrCodeFile.CO_HTML_TEMPLATE_CONVERT_ERROR);
   }
}

生成PDF

/**
  * htmlContent 通過(guò)freemarker生成的html
  * fontPath 字體文件路徑
  * ByteArrayOutputStream pdf文件流
  */
private static ByteArrayOutputStream htmlToPdf(String htmlContent, String fontPath) {
   try {
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      ITextRenderer render = new ITextRenderer();
      ITextFontResolver fontResolver = render.getFontResolver();
      fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
      render.setDocumentFromString(htmlContent);
      render.getSharedContext().setBaseURL(BASE_URL);
      render.layout();
      render.createPDF(output);
      return output;
   } catch (Exception e) {
      log.debug("html轉(zhuǎn)換pdf失敗!", e);
      throw new RuntimeException("html轉(zhuǎn)換pdf失敗!", e);
   }
}

添加水印和頁(yè)碼

從文件獲取水印

/**
  * outputStream 生成的pdf流
  * waterMarkPath 水印文件路徑
  * fontPath 字體路徑
  * OutputStream pdf文件流
  */
private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, String waterMarkPath,
      String fontPath) {
   BaseFont baseFont = createFont(fontPath);
   try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      PdfReader reader = new PdfReader(input);
      PdfStamper stamp = new PdfStamper(reader, output);
      PdfContentByte contentByte = null;
      int n = reader.getNumberOfPages();
      Image logo = null;
      if(StringUtils.isNotBlank(waterMarkPath)){
          logo = Image.getInstance(waterMarkPath);
      }
      for (int i = 1; i <= n; i++) {
         contentByte = stamp.getUnderContent(i);
         Rectangle rectangle = reader.getPageSize(i);
         float width = rectangle.getWidth();
         float height = rectangle.getHeight();
         if(logo != null){
            logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
            contentByte.addImage(logo);
            contentByte.saveState();
         }
         String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
         contentByte.beginText();
         contentByte.setFontAndSize(baseFont, 12);
         contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
         contentByte.endText();
      }
      reader.close();
      stamp.close();
      return output;
   } catch (Exception e) {
      log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
      throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
   }
}
private static BaseFont createFont(String fontPath) {
   try {
      return BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
   } catch (Exception e) {
      log.debug("字體讀取失敗," + fontPath, e);
      throw new RuntimeException("字體讀取失敗," + fontPath, e);
   }
}

從流獲取水印

/**
  * outputStream 生成的pdf流
  * waterMarkPath 水印流
  * fontPath 字體路徑
  * OutputStream pdf文件流
  */
private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, InputStream waterMarkPath,
      String fontPath) {
   BaseFont baseFont = createFont(fontPath);
   try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        PdfReader reader = new PdfReader(input);
        PdfStamper stamp = new PdfStamper(reader, output);
        PdfContentByte contentByte = null;
        int n = reader.getNumberOfPages();
        Image logo = null;
        if(waterMarkPath != null){
            byte[] waterMarkBytes = IOUtils.toByteArray(inputStream);
            logo = Image.getInstance(waterMarkBytes);
        }
        for (int i = 1; i <= n; i++) {
            contentByte = stamp.getUnderContent(i);
            Rectangle rectangle = reader.getPageSize(i);
            float width = rectangle.getWidth();
            float height = rectangle.getHeight();
            if(logo != null){
                logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
                contentByte.addImage(logo);
                contentByte.saveState();
            }
            String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
            contentByte.beginText();
            contentByte.setFontAndSize(baseFont, 12);
            contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
            contentByte.endText();
        }
         reader.close();
         stamp.close();
        return output;
    } catch (Exception e) {
      log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
      throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
    }
}

pdf加密碼及權(quán)限設(shè)置

權(quán)限說(shuō)明

權(quán)限 說(shuō)明
ALLOW_PRINTING 文檔允許打印
ALLOW_DEGRADED_PRINTING 允許用戶(hù)打印文檔,但不提供allow_printing質(zhì)量(128位加密)
ALLOW_MODIFY_CONTENTS 允許用戶(hù)修改內(nèi)容,例如 更改頁(yè)面內(nèi)容,或插入或刪除頁(yè)
ALLOW_ASSEMBLY 允許用戶(hù)插入、刪除和旋轉(zhuǎn)頁(yè)面和添加書(shū)簽。頁(yè)面的內(nèi)容不能更改,除非也授予allow_modify_contents權(quán)限,(128位加密)
ALLOW_COPY 允許用戶(hù)復(fù)制或以其他方式從文檔中提取文本和圖形,包括使用輔助技術(shù)。例如屏幕閱讀器或其他可訪問(wèn)設(shè)備
ALLOW_SCREENREADERS 允許用戶(hù)提取文本和圖形以供易訪問(wèn)性設(shè)備使用,(128位加密)
ALLOW_MODIFY_ANNOTATIONS 允許用戶(hù)添加或修改文本注釋和交互式表單字段
ALLOW_FILL_IN 允許用戶(hù)填寫(xiě)表單字段,(128位加密)

需要多個(gè)權(quán)限時(shí),用|拼接即可

無(wú)水印頁(yè)碼

在生成PDF時(shí)添加權(quán)限及密碼

private static ByteArrayOutputStream htmlToPdf(String htmlContent, String fontPath, String password, String adminPassword) {
   try {
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      ITextRenderer render = new ITextRenderer();
      ITextFontResolver fontResolver = render.getFontResolver();
      setPDFEncryption(password, adminPassword, render);
      fontResolver.addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
      render.setDocumentFromString(htmlContent);
      render.getSharedContext().setBaseURL(BASE_URL);
      render.layout();
      render.createPDF(output);
      return output;
   } catch (Exception e) {
      log.debug("html轉(zhuǎn)換pdf失敗!", e);
      throw new RuleException(ErrCodeFile.CO_HTML_TO_PDF_FAILED);
   }
}
/** 用戶(hù)權(quán)限,根據(jù)需求自己設(shè)置*/
private static final int PERMIT = PdfWriter.ALLOW_PRINTING;
/** pdf加密類(lèi)型*/
private static final int ENCRYPTION_TYPE = PdfWriter.STANDARD_ENCRYPTION_128;
private static void setPDFEncryption(String password, String adminPassword,ITextRenderer render) {
    PDFEncryption pdfEncryption = new PDFEncryption();
    // 用戶(hù)密碼
    pdfEncryption.setUserPassword(password.getBytes());
    // 管理員密碼
    pdfEncryption.setOwnerPassword(adminPassword.getBytes());
    // 用戶(hù)權(quán)限
    pdfEncryption.setAllowedPrivileges(PERMIT);
    // 加密類(lèi)型
    pdfEncryption.setEncryptionType(ENCRYPTION_TYPE);
    render.setPDFEncryption(pdfEncryption);
}

有水印頁(yè)碼

在生成PDF時(shí)無(wú)需添加權(quán)限及密碼,在添加水印及頁(yè)碼時(shí)添加

private static OutputStream addWaterImage(ByteArrayOutputStream outputStream, InputStream waterMarkPath,
      String fontPath, String password, String adminPassword) {
   BaseFont baseFont = createFont(fontPath);
   try (InputStream input = new ByteArrayInputStream(outputStream.toByteArray());) {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        PdfReader reader = new PdfReader(input);
        PdfStamper stamp = new PdfStamper(reader, output);
        // 用戶(hù)密碼,管理員密碼,權(quán)限,加密類(lèi)型
        stamp.setEncryption(password.getBytes(), adminPassword.getBytes(), PERMIT, ENCRYPTION_TYPE);
        PdfContentByte contentByte = null;
        int n = reader.getNumberOfPages();
        Image logo = null;
        if(waterMarkPath != null){
            byte[] waterMarkBytes = IOUtils.toByteArray(inputStream);
            logo = Image.getInstance(waterMarkBytes);
        }
        for (int i = 1; i <= n; i++) {
            contentByte = stamp.getUnderContent(i);
            Rectangle rectangle = reader.getPageSize(i);
            float width = rectangle.getWidth();
            float height = rectangle.getHeight();
            if(logo != null){
                logo.setAbsolutePosition(width / 2 - logo.getWidth() / 2, height / 2);
                contentByte.addImage(logo);
                contentByte.saveState();
            }
            String text = "第 " + i + " 頁(yè) /共 " + n + " 頁(yè)";
            contentByte.beginText();
            contentByte.setFontAndSize(baseFont, 12);
            contentByte.showTextAligned(Element.ALIGN_CENTER, text, (width / 2) - 6, 15, 0);
            contentByte.endText();
        }
         reader.close();
         stamp.close();
        return output;
    } catch (Exception e) {
      log.debug("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
      throw new RuntimeException("添加水印和頁(yè)碼失敗," + waterMarkPath, e);
    }
}

模板記錄

1、字符集亂碼問(wèn)題

<meta http-equiv="Content-Type" content="text/html; charset=GBK"/>

(歷史原因?qū)е麻_(kāi)發(fā)使用GBK字符集,UTF-8的情況暫時(shí)未知)charset應(yīng)與Java代碼中的傳入的保持一致,同時(shí)ftl文件的字符集應(yīng)與此保持一致,windows使用GBK,linux使用UTF-8,否則生成PDF亂碼

2、img標(biāo)簽

<img src="${logoImage}" width="204"/>

img標(biāo)簽支持base64格式data:image/png;base64,

data:,文本數(shù)據(jù)
data:text/plain,文本數(shù)據(jù)
data:text/html,HTML代碼
data:text/html;base64,base64編碼的HTML代碼
data:text/css,CSS代碼
data:text/css;base64,base64編碼的CSS代碼
data:text/javascript,Javascript代碼
data:text/javascript;base64,base64編碼的Javascript代碼
data:image/gif;base64,base64編碼的gif圖片數(shù)據(jù)
data:image/png;base64,base64編碼的png圖片數(shù)據(jù)
data:image/jpeg;base64,base64編碼的jpeg圖片數(shù)據(jù)
data:image/x-icon;base64,base64編碼的icon圖片數(shù)據(jù)

3、強(qiáng)制分頁(yè)

<p style="margin: 0pt">
    <div style="page-break-before: always; clear: both"/>
</p>

4、 head記錄

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=GBK"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <title>xxxx</title>
    <style type='text/css'>
        body {
            font-family: SimSun;
            padding-top: 50px;
        }

        @page {
            size: a4;
            @top-center {
                content: element(header);
            }
            @bottom-center {
                content: element(footer);
            }

        }

        div.header {
            display: block;
            /*text-align: center;*/
            position: running(header);
            width: 100%;
        }

        div.footer {
            display: block;
            text-align: center;
            position: running(footer);
            width: 100%;
        }

        .custom-page-start {
            margin-top: 50px;
        }

        table {
            border-collapse: collapse;
            margin: 0 auto;
            width: 100%;
        }

        td {
            border: #000000 solid 0.75pt;
            vertical-align: top;
            padding: 5pt;
        }

        p {
            line-height: 18pt;
            margin: 0pt 0pt 4pt;
        }

        span {
            font-size: 10pt;
        }

        @media print {
            table {
                page-break-after: auto
            }

            tr {
                page-break-inside: avoid;
                page-break-after: auto
            }

            td {
                page-break-inside: avoid;
                page-break-after: auto
            }

            thead {
                display: table-header-group
            }

            tfoot {
                display: table-footer-group
            }
        }       
    </style>
</head>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容